Merge pull request #479 from coreos/update-ignition-cloud-ct

Update Ignition, Container Linux Config transpiler, and Cloud-init
This commit is contained in:
Dalton Hubble
2017-04-18 11:38:02 -07:00
committed by GitHub
80 changed files with 3803 additions and 778 deletions

View File

@@ -1,7 +1,7 @@
# Cloud config
**Note:** We recommend migrating to [Ignition](ignition.md) for hardware provisioning.
**Note:** We recommend migrating to [Container Linux Configs](container-linux-config.md) for hardware provisioning.
CoreOS Cloud-Config is a system for configuring machines with a Cloud-Config file or executable script from user-data. Cloud-Config runs in userspace on each boot and implements a subset of the [cloud-init spec](http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data). See the cloud-config [docs](https://coreos.com/os/docs/latest/cloud-config.html) for details.

View File

@@ -0,0 +1,144 @@
# Container Linux Configs
A Container Linux Config is a YAML document which declares how Container Linux instances' disks should be provisioned on network boot and first-boot from disk. Configs can declare disk paritions, write files (regular files, systemd units, networkd units, etc.), and configure users. See the Container Linux Config [spec](https://coreos.com/os/docs/latest/configuration.html).
### Ignition
Container Linux Configs are validated and converted to *machine-friendly* Ignition configs (JSON) by matchbox when serving to booting machines. [Ignition](https://coreos.com/ignition/docs/latest/), the provisioning utility shipped in Container Linux, will parse and execute the Ignition config to realize the desired configuration. Matchbox users usually only need to write Container Linux Configs.
*Note: Container Linux directory names are still named "ignition" for historical reasons as outlined below. A future breaking change will rename to "container-linux-config".*
## Adding Container Linux Configs
Container Linux Config templates can be added to the `/var/lib/matchbox/ignition` directory or in an `ignition` subdirectory of a custom `-data-path`. Template files may contain [Go template](https://golang.org/pkg/text/template/) elements which will be evaluated with group metadata, selectors, and query params.
```
/var/lib/matchbox
├── cloud
├── ignition
│   └── k8s-controller.yaml
│   └── etcd.yaml
│   └── k8s-worker.yaml
│   └── raw.ign
└── profiles
```
## Referencing in Profiles
Profiles can include a Container Linux Config for provisioning machines. Specify the Container Linux Config in a [Profile](matchbox.md#profiles) with `ignition_id`. When PXE booting, use the kernel option `coreos.first_boot=1` and `coreos.config.url` to point to the `matchbox` [Ignition endpoint](api.md#ignition-config).
## Examples
Here is an example Container Linux Config template. Variables will be interpreted using group metadata, selectors, and query params. Matchbox will convert the config to Ignition to serve Container Linux machines.
ignition/format-disk.yaml.tmpl:
<!-- {% raw %} -->
```yaml
---
storage:
disks:
- device: /dev/sda
wipe_table: true
partitions:
- label: ROOT
filesystems:
- name: root
mount:
device: "/dev/sda1"
format: "ext4"
create:
force: true
options:
- "-LROOT"
files:
- filesystem: root
path: /home/core/foo
mode: 0644
user:
id: 500
group:
id: 500
contents:
inline: |
{{.example_contents}}
{{ if index . "ssh_authorized_keys" }}
passwd:
users:
- name: core
ssh_authorized_keys:
{{ range $element := .ssh_authorized_keys }}
- {{$element}}
{{end}}
{{end}}
```
<!-- {% endraw %} -->
The Ignition config response (formatted) to a query `/ignition?label=value` for a CoreOS instance supporting Ignition 2.0.0 would be:
```json
{
"ignition": {
"version": "2.0.0",
"config": {}
},
"storage": {
"disks": [
{
"device": "/dev/sda",
"wipeTable": true,
"partitions": [
{
"label": "ROOT",
"number": 0,
"size": 0,
"start": 0
}
]
}
],
"filesystems": [
{
"name": "root",
"mount": {
"device": "/dev/sda1",
"format": "ext4",
"create": {
"force": true,
"options": [
"-LROOT"
]
}
}
}
],
"files": [
{
"filesystem": "root",
"path": "/home/core/foo",
"contents": {
"source": "data:,Example%20file%20contents%0A",
"verification": {}
},
"mode": 420,
"user": {
"id": 500
},
"group": {
"id": 500
}
}
]
},
"systemd": {},
"networkd": {},
"passwd": {}
}
```
See [examples/ignition](../examples/ignition) for numerous Container Linux Config template examples.
### Raw Ignition
If you prefer to design your own templating solution, raw Ignition files (suffixed with `.ign` or `.ignition`) are served directly.

View File

@@ -1,169 +0,0 @@
# Ignition
Ignition is a system for declaratively provisioning disks during the initramfs, before systemd starts. It runs only on the first boot and handles partitioning disks, formatting partitions, writing files (regular files, systemd units, networkd units, etc.), and configuring users. See the Ignition [docs](https://coreos.com/ignition/docs/latest/) for details.
## Fuze configs
Ignition 2.0.0+ configs are versioned, *machine-friendly* JSON documents (which contain encoded file contents). Operators should write and maintain configs in a *human-friendly* format, such as CoreOS [fuze](https://github.com/coreos/fuze) configs. As of `matchbox` v0.4.0, Fuze configs are the primary way to use CoreOS Ignition.
The [Fuze schema](https://github.com/coreos/fuze/blob/master/doc/configuration.md) formalizes and improves upon the YAML to Ignition JSON transform. Fuze provides better support for Ignition 2.0.0+, handles file content encoding, patches Ignition bugs, performs better validations, and lets services (like `matchbox`) negotiate the Ignition version required by a CoreOS client.
### Adding Fuze configs
Fuze template files can be added in the `/var/lib/matchbox/ignition` directory or in an `ignition` subdirectory of a custom `-data-path`. Template files may contain [Go template](https://golang.org/pkg/text/template/) elements which will be evaluated with group metadata, selectors, and query params.
```
/var/lib/matchbox
├── cloud
├── ignition
│   └── k8s-controller.yaml
│   └── etcd.yaml
│   └── k8s-worker.yaml
│   └── raw.ign
└── profiles
```
### Reference
Reference an Fuze config in a [Profile](matchbox.md#profiles) with `ignition_id`. When PXE booting, use the kernel option `coreos.first_boot=1` and `coreos.config.url` to point to the `matchbox` [Ignition endpoint](api.md#ignition-config).
### Migration from v0.3.0
In v0.4.0, `matchbox` switched to using the CoreOS [fuze](https://github.com/coreos/fuze) library, which formalizes and improves upon the YAML to Ignition JSON transform. Fuze provides better support for Ignition 2.0.0+, handles file content encoding, patches Ignition bugs, and performs better validations.
Upgrade your Ignition YAML templates to match the [Fuze config schema](https://github.com/coreos/fuze/blob/master/doc/configuration.md). Typically, you'll need to do the following:
* Remove `ignition_version: 1`, Fuze configs are version-less
* Update `filesystems` section and set the `name`
* Update `files` section to use `inline` as shown below
* Replace `uid` and `gid` with `user` and `group` objects as shown above
Maintain readable inline file contents in Fuze:
```
...
files:
- path: /etc/foo.conf
filesystem: root
contents:
inline: |
foo bar
```
Support for the older Ignition v1 format has been dropped, so CoreOS machines must be **1010.1.0 or newer**. Read the upstream Ignition v1 to 2.0.0 [migration guide](https://coreos.com/ignition/docs/latest/migrating-configs.html) to understand the reasons behind schema changes.
## Examples
Here is an example Fuze template. This template will be rendered into a Fuze config (YAML), using group metadata, selectors, and query params as template variables. Finally, the Fuze config is served to client machines as Ignition JSON.
ignition/format-disk.yaml.tmpl:
<!-- {% raw %} -->
```yaml
---
storage:
disks:
- device: /dev/sda
wipe_table: true
partitions:
- label: ROOT
filesystems:
- name: root
mount:
device: "/dev/sda1"
format: "ext4"
create:
force: true
options:
- "-LROOT"
files:
- filesystem: root
path: /home/core/foo
mode: 0644
user:
id: 500
group:
id: 500
contents:
inline: |
{{.example_contents}}
{{ if index . "ssh_authorized_keys" }}
passwd:
users:
- name: core
ssh_authorized_keys:
{{ range $element := .ssh_authorized_keys }}
- {{$element}}
{{end}}
{{end}}
```
<!-- {% endraw %} -->
The Ignition config response (formatted) to a query `/ignition?label=value` for a CoreOS instance supporting Ignition 2.0.0 would be:
```json
{
"ignition": {
"version": "2.0.0",
"config": {}
},
"storage": {
"disks": [
{
"device": "/dev/sda",
"wipeTable": true,
"partitions": [
{
"label": "ROOT",
"number": 0,
"size": 0,
"start": 0
}
]
}
],
"filesystems": [
{
"name": "root",
"mount": {
"device": "/dev/sda1",
"format": "ext4",
"create": {
"force": true,
"options": [
"-LROOT"
]
}
}
}
],
"files": [
{
"filesystem": "root",
"path": "/home/core/foo",
"contents": {
"source": "data:,Example%20file%20contents%0A",
"verification": {}
},
"mode": 420,
"user": {
"id": 500
},
"group": {
"id": 500
}
}
]
},
"systemd": {},
"networkd": {},
"passwd": {}
}
```
See [examples/ignition](../examples/ignition) for numerous Fuze template examples.
### Raw Ignition
If you prefer to design your own templating solution, raw Ignition files (suffixed with `.ign` or `.ignition`) are served directly.

View File

@@ -130,16 +130,16 @@ Group selectors can use any key/value pairs you find useful. However, several la
### Config templates
Profiles can reference various templated configs. Ignition JSON configs can be generated from [Fuze config](https://github.com/coreos/fuze/blob/master/doc/configuration.md) template files. Cloud-Config templates files can be used to render a script or Cloud-Config. Generic template files can be used to render arbitrary untyped configs (experimental). Each template may contain [Go template](https://golang.org/pkg/text/template/) elements which will be rendered with machine group metadata, selectors, and query params.
Profiles can reference various templated configs. Ignition JSON configs can be generated from [Container Linux Config](https://github.com/coreos/container-linux-config-transpiler/blob/master/doc/configuration.md) template files. Cloud-Config templates files can be used to render a script or Cloud-Config. Generic template files can be used to render arbitrary untyped configs (experimental). Each template may contain [Go template](https://golang.org/pkg/text/template/) elements which will be rendered with machine group metadata, selectors, and query params.
For details and examples:
* [Ignition Config](ignition.md)
* [Container Linux Config](container-linux-config.md)
* [Cloud-Config](cloud-config.md)
#### Variables
Within Ignition/Fuze templates, Cloud-Config templates, or generic templates, you can use group metadata, selectors, or request-scoped query params. For example, a request `/generic?mac=52-54-00-89-d8-10&foo=some-param&bar=b` would match the `node1.json` machine group shown above. If the group's profile ("etcd") referenced a generic template, the following variables could be used.
Within Container Linux Config templates, Cloud-Config templates, or generic templates, you can use group metadata, selectors, or request-scoped query params. For example, a request `/generic?mac=52-54-00-89-d8-10&foo=some-param&bar=b` would match the `node1.json` machine group shown above. If the group's profile ("etcd") referenced a generic template, the following variables could be used.
<!-- {% raw %} -->
```

View File

@@ -21,7 +21,7 @@ Network boot and provision CoreOS clusters on virtual or physical hardware.
* [Profiles](Documentation/matchbox.md#profiles)
* [Groups](Documentation/matchbox.md#groups)
* Config Templates
* [Ignition](Documentation/ignition.md)
* [Container Linux Config](Documentation/container-linux-config.md)
* [Cloud-Config](Documentation/cloud-config.md)
* [Configuration](Documentation/config.md)
* [HTTP API](Documentation/api.md)

26
glide.lock generated
View File

@@ -1,38 +1,42 @@
hash: 9b6db105e9175308cb3a911161d68bbc80a5ee762bb7f08c926101cc5389b115
updated: 2017-04-10T15:46:19.530989294-07:00
hash: 205de0b66ed059a1f10d3fb36c7d465439818123940a9aaa68ddc71cc3bbfddd
updated: 2017-04-17T17:09:48.864562358-07:00
imports:
- name: github.com/ajeddeloh/go-json
version: 73d058cf8437a1989030afe571eeab9f90eebbbd
- name: github.com/ajeddeloh/yaml
version: 1072abfea31191db507785e2e0c1b8d1440d35a5
- name: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- name: github.com/camlistore/camlistore
version: 9106ce829629773474c689b34aacd7d3aaa99426
- name: github.com/coreos/coreos-cloudinit
version: 4c333e657bfbaa8f6594298b48324f45e6bf5961
subpackages:
- config
- name: github.com/coreos/fuze
version: 63c72bc1c8875f7f4ca11800a1a8de0478a69a12
- name: github.com/coreos/container-linux-config-transpiler
version: 12554ca0a5ce8ea4a6c594242ccb23d8b9bff493
subpackages:
- config
- config/templating
- config/types
- name: github.com/coreos/coreos-cloudinit
version: 5be99bf577f2768193c7fb587ef5a8806c1503cf
subpackages:
- config
- name: github.com/coreos/go-semver
version: 294930c1e79c64e7dbe360054274fdad492c8cf5
version: 5e3acbb5668c4c3deb4842615c4098eb61fb6b1e
subpackages:
- semver
- name: github.com/coreos/go-systemd
version: 43e4800a6165b4e02bb2a36673c54b230d6f7b26
subpackages:
- journal
- unit
- name: github.com/coreos/ignition
version: 3ffd793b1292c6b0b3519bce214bdb41f336faa7
version: d75d0aa3bf307f0954ce4ea8cac56dacec8d16ce
subpackages:
- config
- config/types
- config/v1
- config/v1/types
- config/v2_0
- config/v2_0/types
- config/validate
- config/validate/astjson
- config/validate/report

View File

@@ -17,9 +17,15 @@ import:
- naming
- peer
- transport
# Ignition and Fuze parse machine configs
# Container Linux Config Transpiler and Ignition
- package: github.com/coreos/container-linux-config-transpiler
version: v0.2.2
subpackages:
- config
- config/types
- config/templating
- package: github.com/coreos/ignition
version: 3ffd793b1292c6b0b3519bce214bdb41f336faa7
version: d75d0aa3bf307f0954ce4ea8cac56dacec8d16ce
subpackages:
- config
- config/types
@@ -28,17 +34,18 @@ import:
- config/validate
- config/validate/astjson
- config/validate/report
- package: github.com/coreos/fuze
version: 63c72bc1c8875f7f4ca11800a1a8de0478a69a12
subpackages:
- config
- config/types
- package: github.com/ajeddeloh/yaml
version: 1072abfea31191db507785e2e0c1b8d1440d35a5
- package: github.com/vincent-petithory/dataurl
version: 9a301d65acbb728fcc3ace14f45f511a4cfeea9c
- package: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/coreos/go-semver
version: 5e3acbb5668c4c3deb4842615c4098eb61fb6b1e
subpackages:
- semver
- package: github.com/coreos/coreos-cloudinit
version: v1.11.0
version: v1.13.0
subpackages:
- config
- package: github.com/coreos/pkg
@@ -71,14 +78,8 @@ import:
version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
- package: github.com/coreos/yaml
version: 6b16a5714269b2f70720a45406b1babd947a17ef
- package: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
- package: github.com/camlistore/camlistore
version: 9106ce829629773474c689b34aacd7d3aaa99426
- package: github.com/coreos/go-semver
version: 294930c1e79c64e7dbe360054274fdad492c8cf5
subpackages:
- semver
- package: github.com/coreos/go-systemd
version: v12
subpackages:

View File

@@ -7,7 +7,7 @@ import (
"context"
"github.com/Sirupsen/logrus"
fuze "github.com/coreos/fuze/config"
ct "github.com/coreos/container-linux-config-transpiler/config"
ignition "github.com/coreos/ignition/config"
"github.com/coreos/matchbox/matchbox/server"
@@ -16,9 +16,9 @@ import (
// ignitionHandler returns a handler that responds with the Ignition config
// matching the request. The Ignition file referenced in the Profile is parsed
// as raw Ignition (for .ign/.ignition) or rendered to a Fuze config (YAML)
// and converted to Ignition. Ignition configs are served as HTTP JSON
// responses.
// as raw Ignition (for .ign/.ignition) or rendered from a Container Linux
// Config (YAML) and converted to Ignition. Ignition configs are served as HTTP
// JSON responses.
func (s *Server) ignitionHandler(core server.Server) ContextHandler {
fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
group, err := groupFromContext(ctx)
@@ -48,7 +48,7 @@ func (s *Server) ignitionHandler(core server.Server) ContextHandler {
"group": group.Id,
"group_name": group.Name,
"profile": group.Profile,
}).Infof("No Ignition or Fuze template named: %s", profile.IgnitionId)
}).Infof("No Ignition or Container Linux Config template named: %s", profile.IgnitionId)
http.NotFound(w, req)
return
}
@@ -58,7 +58,7 @@ func (s *Server) ignitionHandler(core server.Server) ContextHandler {
"labels": labelsFromRequest(nil, req),
"group": group.Id,
"profile": profile.Id,
}).Debug("Matched an Ignition or Fuze template")
}).Debug("Matched an Ignition or Container Linux Config template")
// Skip rendering if raw Ignition JSON is provided
if isIgnition(profile.IgnitionId) {
@@ -70,7 +70,7 @@ func (s *Server) ignitionHandler(core server.Server) ContextHandler {
return
}
// Fuze Config template
// Container Linux Config template
// collect data for rendering
data, err := collectVariables(req, group)
@@ -88,18 +88,18 @@ func (s *Server) ignitionHandler(core server.Server) ContextHandler {
return
}
// Parse bytes into a Fuze Config
config, report := fuze.Parse(buf.Bytes())
// Parse bytes into a Container Linux Config
config, report := ct.Parse(buf.Bytes())
if report.IsFatal() {
s.logger.Errorf("error parsing Fuze config: %s", report.String())
s.logger.Errorf("error parsing Container Linux config: %s", report.String())
http.NotFound(w, req)
return
}
// Convert Fuze Config into an Ignition Config
ign, report := fuze.ConvertAs2_0_0(config)
// Convert Container Linux Config into an Ignition Config
ign, report := ct.ConvertAs2_0(config, "")
if report.IsFatal() {
s.logger.Errorf("error converting Fuze config: %s", report.String())
s.logger.Errorf("error converting Container Linux config: %s", report.String())
http.NotFound(w, req)
return
}

View File

@@ -40,7 +40,7 @@ func TestIgnitionHandler_V2JSON(t *testing.T) {
}
func TestIgnitionHandler_V2YAML(t *testing.T) {
// exercise templating features, not a realistic Fuze template
// exercise templating features, not a realistic Container Linux Config template
content := `
systemd:
units:
@@ -66,7 +66,7 @@ systemd:
req, _ := http.NewRequest("GET", "/?foo=some-param&bar=b", nil)
h.ServeHTTP(ctx, w, req)
// assert that:
// - Fuze template rendered with Group selectors, metadata, and query variables
// - Container Linux Config template rendered with Group selectors, metadata, and query variables
// - Transformed to an Ignition config (JSON)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, jsonContentType, w.HeaderMap.Get(contentType))

View File

@@ -26,35 +26,35 @@ var (
ErrNotDocumentNode = errors.New("Can only convert from document node")
)
type YamlNode struct {
type yamlNode struct {
key yaml.Node
yaml.Node
}
func FromYamlDocumentNode(n yaml.Node) (YamlNode, error) {
func fromYamlDocumentNode(n yaml.Node) (yamlNode, error) {
if n.Kind != yaml.DocumentNode {
return YamlNode{}, ErrNotDocumentNode
return yamlNode{}, ErrNotDocumentNode
}
return YamlNode{
return yamlNode{
key: n,
Node: *n.Children[0],
}, nil
}
func (n YamlNode) ValueLineCol(source io.ReadSeeker) (int, int, string) {
func (n yamlNode) ValueLineCol(source io.ReadSeeker) (int, int, string) {
return n.Line, n.Column, ""
}
func (n YamlNode) KeyLineCol(source io.ReadSeeker) (int, int, string) {
func (n yamlNode) KeyLineCol(source io.ReadSeeker) (int, int, string) {
return n.key.Line, n.key.Column, ""
}
func (n YamlNode) LiteralValue() interface{} {
func (n yamlNode) LiteralValue() interface{} {
return n.Value
}
func (n YamlNode) SliceChild(index int) (validate.AstNode, bool) {
func (n yamlNode) SliceChild(index int) (validate.AstNode, bool) {
if n.Kind != yaml.SequenceNode {
return nil, false
}
@@ -62,13 +62,13 @@ func (n YamlNode) SliceChild(index int) (validate.AstNode, bool) {
return nil, false
}
return YamlNode{
return yamlNode{
key: yaml.Node{},
Node: *n.Children[index],
}, true
}
func (n YamlNode) KeyValueMap() (map[string]validate.AstNode, bool) {
func (n yamlNode) KeyValueMap() (map[string]validate.AstNode, bool) {
if n.Kind != yaml.MappingNode {
return nil, false
}
@@ -77,7 +77,7 @@ func (n YamlNode) KeyValueMap() (map[string]validate.AstNode, bool) {
for i := 0; i < len(n.Children); i += 2 {
key := *n.Children[i]
value := *n.Children[i+1]
kvmap[key.Value] = YamlNode{
kvmap[key.Value] = yamlNode{
key: key,
Node: value,
}
@@ -85,6 +85,6 @@ func (n YamlNode) KeyValueMap() (map[string]validate.AstNode, bool) {
return kvmap, true
}
func (n YamlNode) Tag() string {
func (n yamlNode) Tag() string {
return "yaml"
}

View File

@@ -18,7 +18,8 @@ import (
"reflect"
yaml "github.com/ajeddeloh/yaml"
"github.com/coreos/fuze/config/types"
"github.com/coreos/container-linux-config-transpiler/config/types"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -39,7 +40,7 @@ func Parse(data []byte) (types.Config, report.Report) {
})
r.Merge(validate.ValidateWithoutSource(reflect.ValueOf(cfg)))
} else {
root, err := FromYamlDocumentNode(*nodes)
root, err := fromYamlDocumentNode(*nodes)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
}
@@ -52,3 +53,7 @@ func Parse(data []byte) (types.Config, report.Report) {
}
return cfg, r
}
func ConvertAs2_0(in types.Config, platform string) (ignTypes.Config, report.Report) {
return types.ConvertAs2_0(in, platform)
}

View File

@@ -0,0 +1,135 @@
// Copyright 2017 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 templating
import (
"fmt"
"strings"
)
var (
ErrUnknownPlatform = fmt.Errorf("unsupported platform")
ErrUnknownField = fmt.Errorf("unknown field")
)
const (
PlatformAzure = "azure"
PlatformDO = "digitalocean"
PlatformEC2 = "ec2"
PlatformGCE = "gce"
PlatformPacket = "packet"
PlatformOpenStackMetadata = "openstack-metadata"
)
var Platforms = []string{
PlatformAzure,
PlatformDO,
PlatformEC2,
PlatformGCE,
PlatformPacket,
PlatformOpenStackMetadata,
}
const (
fieldHostname = "HOSTNAME"
fieldV4Private = "PRIVATE_IPV4"
fieldV4Public = "PUBLIC_IPV4"
fieldV6Private = "PRIVATE_IPV6"
fieldV6Public = "PUBLIC_IPV6"
)
var platformTemplatingMap = map[string]map[string]string{
PlatformAzure: {
// TODO: is this right?
fieldV4Private: "COREOS_AZURE_IPV4_DYNAMIC",
fieldV4Public: "COREOS_AZURE_IPV4_VIRTUAL",
},
PlatformDO: {
// TODO: unused: COREOS_DIGITALOCEAN_IPV4_ANCHOR_0
fieldHostname: "COREOS_DIGITALOCEAN_HOSTNAME",
fieldV4Private: "COREOS_DIGITALOCEAN_IPV4_PRIVATE_0",
fieldV4Public: "COREOS_DIGITALOCEAN_IPV4_PUBLIC_0",
fieldV6Private: "COREOS_DIGITALOCEAN_IPV6_PRIVATE_0",
fieldV6Public: "COREOS_DIGITALOCEAN_IPV6_PUBLIC_0",
},
PlatformEC2: {
fieldHostname: "COREOS_EC2_HOSTNAME",
fieldV4Private: "COREOS_EC2_IPV4_LOCAL",
fieldV4Public: "COREOS_EC2_IPV4_PUBLIC",
},
PlatformGCE: {
fieldHostname: "COREOS_GCE_HOSTNAME",
fieldV4Private: "COREOS_GCE_IP_EXTERNAL_0",
fieldV4Public: "COREOS_GCE_IP_LOCAL_0",
},
PlatformPacket: {
fieldHostname: "COREOS_PACKET_HOSTNAME",
fieldV4Private: "COREOS_PACKET_IPV4_PRIVATE_0",
fieldV4Public: "COREOS_PACKET_IPV4_PUBLIC_0",
fieldV6Public: "COREOS_PACKET_IPV6_PUBLIC_0",
},
PlatformOpenStackMetadata: {
fieldHostname: "COREOS_OPENSTACK_HOSTNAME",
fieldV4Private: "COREOS_OPENSTACK_IPV4_LOCAL",
fieldV4Public: "COREOS_OPENSTACK_IPV4_PUBLIC",
},
}
// HasTemplating returns whether or not any of the environment variables present
// in the passed in list use ct templating
func HasTemplating(vars []string) bool {
for _, v := range vars {
if strings.ContainsRune(v, '{') || strings.ContainsRune(v, '}') {
return true
}
}
return false
}
func PerformTemplating(platform string, vars []string) ([]string, error) {
if _, ok := platformTemplatingMap[platform]; !ok {
return nil, ErrUnknownPlatform
}
for i := range vars {
startIndex := strings.IndexRune(vars[i], '{')
endIndex := strings.IndexRune(vars[i], '}')
for startIndex != -1 && endIndex != -1 && startIndex < endIndex {
fieldName := vars[i][startIndex+1 : endIndex]
fieldVal, ok := platformTemplatingMap[platform][fieldName]
if !ok {
return nil, ErrUnknownField
}
vars[i] = strings.Replace(vars[i], "{"+fieldName+"}", "${"+fieldVal+"}", 1)
// start the search for a new start index from the old end index, or
// we'll just find the curly braces we just substituted in
startIndex = strings.IndexRune(vars[i][endIndex:], '{')
if startIndex != -1 {
startIndex += endIndex
// and start the search for a new end index from the new start
// index, or as before we'll just find the curly braces we just
// substituted in
endIndex = strings.IndexRune(vars[i][startIndex:], '}')
if endIndex != -1 {
endIndex += startIndex
}
}
}
}
return vars, nil
}

View File

@@ -0,0 +1,89 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"reflect"
"github.com/coreos/container-linux-config-transpiler/config/templating"
)
var (
ErrPlatformUnspecified = fmt.Errorf("platform must be specified to use templating")
)
func isZero(v interface{}) bool {
if v == nil {
return true
}
zv := reflect.Zero(reflect.TypeOf(v))
return reflect.DeepEqual(v, zv.Interface())
}
// assembleUnit will assemble the contents of a systemd unit dropin that will
// have the given environment variables, and call the given exec line with the
// provided args prepended to it
func assembleUnit(exec string, args, vars []string, platform string) (string, error) {
hasTemplating := templating.HasTemplating(args)
var out string
if hasTemplating {
if platform == "" {
return "", ErrPlatformUnspecified
}
out = "[Unit]\nRequires=coreos-metadata.service\nAfter=coreos-metadata.service\n\n[Service]\nEnvironmentFile=/run/metadata/coreos\n"
var err error
args, err = templating.PerformTemplating(platform, args)
if err != nil {
return "", err
}
} else {
out = "[Service]\n"
}
for _, v := range vars {
out += fmt.Sprintf("Environment=\"%s\"\n", v)
}
for _, a := range args {
exec += fmt.Sprintf(" \\\n %s", a)
}
out += "ExecStart=\n"
out += fmt.Sprintf("ExecStart=%s", exec)
return out, nil
}
// getCliArgs builds a list of --ARG=VAL from a struct with cli: tags on its members.
func getCliArgs(e interface{}) []string {
if e == nil {
return nil
}
et := reflect.TypeOf(e)
ev := reflect.ValueOf(e)
vars := []string{}
for i := 0; i < et.NumField(); i++ {
if val := ev.Field(i).Interface(); !isZero(val) {
if et.Field(i).Anonymous {
vars = append(vars, getCliArgs(val)...)
} else {
key := et.Field(i).Tag.Get("cli")
vars = append(vars, fmt.Sprintf("--%s=%q", key, val))
}
}
}
return vars
}

View File

@@ -0,0 +1,95 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"net/url"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Config struct {
Ignition Ignition `yaml:"ignition"`
Storage Storage `yaml:"storage"`
Systemd Systemd `yaml:"systemd"`
Networkd Networkd `yaml:"networkd"`
Passwd Passwd `yaml:"passwd"`
Etcd *Etcd `yaml:"etcd"`
Flannel *Flannel `yaml:"flannel"`
Update *Update `yaml:"update"`
Docker *Docker `yaml:"docker"`
Locksmith *Locksmith `yaml:"locksmith"`
}
type Ignition struct {
Config IgnitionConfig `yaml:"config"`
}
type IgnitionConfig struct {
Append []ConfigReference `yaml:"append"`
Replace *ConfigReference `yaml:"replace"`
}
type ConfigReference struct {
Source string `yaml:"source"`
Verification Verification `yaml:"verification"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, ref := range in.Ignition.Config.Append {
newRef, err := convertConfigReference(ref)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
}
out.Ignition.Config.Append = append(out.Ignition.Config.Append, newRef)
}
if in.Ignition.Config.Replace != nil {
newRef, err := convertConfigReference(*in.Ignition.Config.Replace)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
}
out.Ignition.Config.Replace = &newRef
}
return out, report.Report{}
})
}
func convertConfigReference(in ConfigReference) (ignTypes.ConfigReference, error) {
source, err := url.Parse(in.Source)
if err != nil {
return ignTypes.ConfigReference{}, err
}
return ignTypes.ConfigReference{
Source: ignTypes.Url(*source),
Verification: convertVerification(in.Verification),
}, nil
}
func convertVerification(in Verification) ignTypes.Verification {
if in.Hash.Function == "" || in.Hash.Sum == "" {
return ignTypes.Verification{}
}
return ignTypes.Verification{
&ignTypes.Hash{
Function: in.Hash.Function,
Sum: in.Hash.Sum,
},
}
}

View File

@@ -0,0 +1,58 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"reflect"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
type converterFor2_0 func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report)
var convertersFor2_0 []converterFor2_0
func register2_0(f converterFor2_0) {
convertersFor2_0 = append(convertersFor2_0, f)
}
func ConvertAs2_0(in Config, platform string) (ignTypes.Config, report.Report) {
out := ignTypes.Config{
Ignition: ignTypes.Ignition{
Version: ignTypes.IgnitionVersion{Major: 2, Minor: 0},
},
}
r := report.Report{}
for _, convert := range convertersFor2_0 {
var subReport report.Report
out, subReport = convert(in, out, platform)
r.Merge(subReport)
}
if r.IsFatal() {
return ignTypes.Config{}, r
}
validationReport := validate.ValidateWithoutSource(reflect.ValueOf(out))
r.Merge(validationReport)
if r.IsFatal() {
return ignTypes.Config{}, r
}
return out, r
}

View File

@@ -0,0 +1,95 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"github.com/alecthomas/units"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
const (
BYTES_PER_SECTOR = 512
)
type Disk struct {
Device string `yaml:"device"`
WipeTable bool `yaml:"wipe_table"`
Partitions []Partition `yaml:"partitions"`
}
type Partition struct {
Label string `yaml:"label"`
Number int `yaml:"number"`
Size string `yaml:"size"`
Start string `yaml:"start"`
TypeGUID string `yaml:"type_guid"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, disk := range in.Storage.Disks {
newDisk := ignTypes.Disk{
Device: ignTypes.Path(disk.Device),
WipeTable: disk.WipeTable,
}
for _, partition := range disk.Partitions {
size, err := convertPartitionDimension(partition.Size)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
}
start, err := convertPartitionDimension(partition.Start)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
}
newDisk.Partitions = append(newDisk.Partitions, ignTypes.Partition{
Label: ignTypes.PartitionLabel(partition.Label),
Number: partition.Number,
Size: size,
Start: start,
TypeGUID: ignTypes.PartitionTypeGUID(partition.TypeGUID),
})
}
out.Storage.Disks = append(out.Storage.Disks, newDisk)
}
return out, report.Report{}
})
}
func convertPartitionDimension(in string) (ignTypes.PartitionDimension, error) {
if in == "" {
return 0, nil
}
b, err := units.ParseBase2Bytes(in)
if err != nil {
return 0, err
}
if b < 0 {
return 0, fmt.Errorf("invalid dimension (negative): %q", in)
}
// Translate bytes into sectors
sectors := (b / BYTES_PER_SECTOR)
if b%BYTES_PER_SECTOR != 0 {
sectors++
}
return ignTypes.PartitionDimension(uint64(sectors)), nil
}

View File

@@ -0,0 +1,44 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"strings"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Docker struct {
Flags []string `yaml:"flags"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
if in.Docker != nil {
contents := fmt.Sprintf("[Service]\nEnvironment=\"DOCKER_OPTS=%s\"", strings.Join(in.Docker.Flags, " "))
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "docker.service",
Enable: true,
DropIns: []ignTypes.SystemdUnitDropIn{{
Name: "20-clct-docker.conf",
Contents: contents,
}},
})
}
return out, report.Report{}
})
}

View File

@@ -0,0 +1,281 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"fmt"
"github.com/coreos/go-semver/semver"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
var (
EtcdVersionTooOld = errors.New("Etcd version specified is not valid (too old)")
EtcdMinorVersionTooNew = errors.New("Etcd minor version specified is too new, only options available in the previous minor version will be accepted")
EtcdMajorVersionTooNew = errors.New("Etcd version is not valid (too new)")
OldestEtcd = *semver.New("2.3.0")
EtcdDefaultVersion = *semver.New("3.0.0")
)
// Options can be the options for any Etcd version
type Options interface{}
type etcdCommon Etcd
type EtcdVersion semver.Version
func (e *EtcdVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
t := semver.Version(*e)
if err := unmarshal(&t); err != nil {
return err
}
*e = EtcdVersion(t)
return nil
}
func (e EtcdVersion) Validate() report.Report {
v := semver.Version(e)
switch {
case v.LessThan(OldestEtcd):
return report.ReportFromError(EtcdVersionTooOld, report.EntryError)
case v.Major == 2 && v.Minor > 3:
fallthrough
case v.Major == 3 && v.Minor > 1:
return report.ReportFromError(EtcdMinorVersionTooNew, report.EntryWarning)
case v.Major > 3:
return report.ReportFromError(EtcdMajorVersionTooNew, report.EntryError)
}
return report.Report{}
}
func (e EtcdVersion) String() string {
return semver.Version(e).String()
}
// Etcd is a stub for yaml unmarshalling that figures out which
// of the other Etcd structs to use and unmarshals to that. Options needs
// to be an embedded type so that the structure of the yaml tree matches the
// structure of the go config tree
type Etcd struct {
Version *EtcdVersion `yaml:"version"`
Options
}
func (etcd *Etcd) UnmarshalYAML(unmarshal func(interface{}) error) error {
t := etcdCommon(*etcd)
if err := unmarshal(&t); err != nil {
return err
}
*etcd = Etcd(t)
var version semver.Version
if etcd.Version == nil {
version = EtcdDefaultVersion
} else {
version = semver.Version(*etcd.Version)
}
if version.Major == 2 && version.Minor >= 3 {
o := Etcd2{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
} else if version.Major == 3 && version.Minor == 0 {
o := Etcd3_0{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
} else if version.Major == 3 && version.Minor >= 1 {
o := Etcd3_1{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
}
return nil
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
if in.Etcd != nil {
contents, err := etcdContents(*in.Etcd, platform)
if err != nil {
return ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
}
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "etcd-member.service",
Enable: true,
DropIns: []ignTypes.SystemdUnitDropIn{{
Name: "20-clct-etcd-member.conf",
Contents: contents,
}},
})
}
return out, report.Report{}
})
}
// etcdContents creates the string containing the systemd drop in for etcd-member
func etcdContents(etcd Etcd, platform string) (string, error) {
args := getCliArgs(etcd.Options)
var vars []string
if etcd.Version != nil {
vars = []string{fmt.Sprintf("ETCD_IMAGE_TAG=v%s", etcd.Version)}
}
return assembleUnit("/usr/lib/coreos/etcd-wrapper $ETCD_OPTS", args, vars, platform)
}
type Etcd3_0 struct {
Name string `yaml:"name" cli:"name"`
DataDir string `yaml:"data_dir" cli:"data-dir"`
WalDir string `yaml:"wal_dir" cli:"wal-dir"`
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
ListenPeerUrls string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
ListenClientUrls string `yaml:"listen_client_urls" cli:"listen-client-urls"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
MaxWals int `yaml:"max_wals" cli:"max-wals"`
Cors string `yaml:"cors" cli:"cors"`
InitialAdvertisePeerUrls string `yaml:"initial_advertise_peer_urls" cli:"initial-advertise-peer-urls"`
InitialCluster string `yaml:"initial_cluster" cli:"initial-cluster"`
InitialClusterState string `yaml:"initial_cluster_state" cli:"initial-cluster-state"`
InitialClusterToken string `yaml:"initial_cluster_token" cli:"initial-cluster-token"`
AdvertiseClientUrls string `yaml:"advertise_client_urls" cli:"advertise-client-urls"`
Discovery string `yaml:"discovery" cli:"discovery"`
DiscoverySrv string `yaml:"discovery_srv" cli:"discovery-srv"`
DiscoveryFallback string `yaml:"discovery_fallback" cli:"discovery-fallback"`
DiscoveryProxy string `yaml:"discovery_proxy" cli:"discovery-proxy"`
StrictReconfigCheck bool `yaml:"strict_reconfig_check" cli:"strict-reconfig-check"`
AutoCompactionRetention int `yaml:"auto_compaction_retention" cli:"auto-compaction-retention"`
Proxy string `yaml:"proxy" cli:"proxy"`
ProxyFailureWait int `yaml:"proxy_failure_wait" cli:"proxy-failure-wait"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" cli:"proxy-refresh-interval"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" cli:"proxy-dial-timeout"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" cli:"proxy-write-timeout"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" cli:"proxy-read-timeout"`
CaFile string `yaml:"ca_file" cli:"ca-file" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
CertFile string `yaml:"cert_file" cli:"cert-file"`
KeyFile string `yaml:"key_file" cli:"key-file"`
ClientCertAuth bool `yaml:"client_cert_auth" cli:"client-cert-auth"`
TrustedCaFile string `yaml:"trusted_ca_file" cli:"trusted-ca-file"`
AutoTls bool `yaml:"auto_tls" cli:"auto-tls"`
PeerCaFile string `yaml:"peer_ca_file" cli:"peer-ca-file" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
PeerCertFile string `yaml:"peer_cert_file" cli:"peer-cert-file"`
PeerKeyFile string `yaml:"peer_key_file" cli:"peer-key-file"`
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" cli:"peer-client-cert-auth"`
PeerTrustedCaFile string `yaml:"peer_trusted_ca_file" cli:"peer-trusted-ca-file"`
PeerAutoTls bool `yaml:"peer_auto_tls" cli:"peer-auto-tls"`
Debug bool `yaml:"debug" cli:"debug"`
LogPackageLevels string `yaml:"log_package_levels" cli:"log-package-levels"`
ForceNewCluster bool `yaml:"force_new_cluster" cli:"force-new-cluster"`
}
type Etcd3_1 struct {
Name string `yaml:"name" cli:"name"`
DataDir string `yaml:"data_dir" cli:"data-dir"`
WalDir string `yaml:"wal_dir" cli:"wal-dir"`
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
ListenPeerUrls string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
ListenClientUrls string `yaml:"listen_client_urls" cli:"listen-client-urls"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
MaxWals int `yaml:"max_wals" cli:"max-wals"`
Cors string `yaml:"cors" cli:"cors"`
InitialAdvertisePeerUrls string `yaml:"initial_advertise_peer_urls" cli:"initial-advertise-peer-urls"`
InitialCluster string `yaml:"initial_cluster" cli:"initial-cluster"`
InitialClusterState string `yaml:"initial_cluster_state" cli:"initial-cluster-state"`
InitialClusterToken string `yaml:"initial_cluster_token" cli:"initial-cluster-token"`
AdvertiseClientUrls string `yaml:"advertise_client_urls" cli:"advertise-client-urls"`
Discovery string `yaml:"discovery" cli:"discovery"`
DiscoverySrv string `yaml:"discovery_srv" cli:"discovery-srv"`
DiscoveryFallback string `yaml:"discovery_fallback" cli:"discovery-fallback"`
DiscoveryProxy string `yaml:"discovery_proxy" cli:"discovery-proxy"`
StrictReconfigCheck bool `yaml:"strict_reconfig_check" cli:"strict-reconfig-check"`
AutoCompactionRetention int `yaml:"auto_compaction_retention" cli:"auto-compaction-retention"`
Proxy string `yaml:"proxy" cli:"proxy"`
ProxyFailureWait int `yaml:"proxy_failure_wait" cli:"proxy-failure-wait"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" cli:"proxy-refresh-interval"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" cli:"proxy-dial-timeout"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" cli:"proxy-write-timeout"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" cli:"proxy-read-timeout"`
CaFile string `yaml:"ca_file" cli:"ca-file" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
CertFile string `yaml:"cert_file" cli:"cert-file"`
KeyFile string `yaml:"key_file" cli:"key-file"`
ClientCertAuth bool `yaml:"client_cert_auth" cli:"client-cert-auth"`
TrustedCaFile string `yaml:"trusted_ca_file" cli:"trusted-ca-file"`
AutoTls bool `yaml:"auto_tls" cli:"auto-tls"`
PeerCaFile string `yaml:"peer_ca_file" cli:"peer-ca-file" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
PeerCertFile string `yaml:"peer_cert_file" cli:"peer-cert-file"`
PeerKeyFile string `yaml:"peer_key_file" cli:"peer-key-file"`
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" cli:"peer-client-cert-auth"`
PeerTrustedCaFile string `yaml:"peer_trusted_ca_file" cli:"peer-trusted-ca-file"`
PeerAutoTls bool `yaml:"peer_auto_tls" cli:"peer-auto-tls"`
Debug bool `yaml:"debug" cli:"debug"`
LogPackageLevels string `yaml:"log_package_levels" cli:"log-package-levels"`
ForceNewCluster bool `yaml:"force_new_cluster" cli:"force-new-cluster"`
Metrics string `yaml:"metrics" cli:"metrics"`
LogOutput string `yaml:"log_output" cli:"log-output"`
}
type Etcd2 struct {
AdvertiseClientURLs string `yaml:"advertise_client_urls" cli:"advertise-client-urls"`
CAFile string `yaml:"ca_file" cli:"ca-file" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
CertFile string `yaml:"cert_file" cli:"cert-file"`
ClientCertAuth bool `yaml:"client_cert_auth" cli:"client-cert-auth"`
CorsOrigins string `yaml:"cors" cli:"cors"`
DataDir string `yaml:"data_dir" cli:"data-dir"`
Debug bool `yaml:"debug" cli:"debug"`
Discovery string `yaml:"discovery" cli:"discovery"`
DiscoveryFallback string `yaml:"discovery_fallback" cli:"discovery-fallback"`
DiscoverySRV string `yaml:"discovery_srv" cli:"discovery-srv"`
DiscoveryProxy string `yaml:"discovery_proxy" cli:"discovery-proxy"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
EnablePprof bool `yaml:"enable_pprof" cli:"enable-pprof"`
ForceNewCluster bool `yaml:"force_new_cluster" cli:"force-new-cluster"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" cli:"initial-advertise-peer-urls"`
InitialCluster string `yaml:"initial_cluster" cli:"initial-cluster"`
InitialClusterState string `yaml:"initial_cluster_state" cli:"initial-cluster-state"`
InitialClusterToken string `yaml:"initial_cluster_token" cli:"initial-cluster-token"`
KeyFile string `yaml:"key_file" cli:"key-file"`
ListenClientURLs string `yaml:"listen_client_urls" cli:"listen-client-urls"`
ListenPeerURLs string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
LogPackageLevels string `yaml:"log_package_levels" cli:"log-package-levels"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
MaxWALs int `yaml:"max_wals" cli:"max-wals"`
Name string `yaml:"name" cli:"name"`
PeerCAFile string `yaml:"peer_ca_file" cli:"peer-ca-file" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
PeerCertFile string `yaml:"peer_cert_file" cli:"peer-cert-file"`
PeerKeyFile string `yaml:"peer_key_file" cli:"peer-key-file"`
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" cli:"peer-client-cert-auth"`
PeerTrustedCAFile string `yaml:"peer_trusted_ca_file" cli:"peer-trusted-ca-file"`
Proxy string `yaml:"proxy" cli:"proxy" valid:"^(on|off|readonly)$"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" cli:"proxy-dial-timeout"`
ProxyFailureWait int `yaml:"proxy_failure_wait" cli:"proxy-failure-wait"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" cli:"proxy-read-timeout"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" cli:"proxy-refresh-interval"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" cli:"proxy-write-timeout"`
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
StrictReconfigCheck bool `yaml:"strict_reconfig_check" cli:"strict-reconfig-check"`
TrustedCAFile string `yaml:"trusted_ca_file" cli:"trusted-ca-file"`
WalDir string `yaml:"wal_dir" cli:"wal-dir"`
}

View File

@@ -0,0 +1,98 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"net/url"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
type File struct {
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
Mode int `yaml:"mode"`
Contents FileContents `yaml:"contents"`
User FileUser `yaml:"user"`
Group FileGroup `yaml:"group"`
}
type FileContents struct {
Remote Remote `yaml:"remote"`
Inline string `yaml:"inline"`
}
type Remote struct {
Url string `yaml:"url"`
Compression string `yaml:"compression"`
Verification Verification `yaml:"verification"`
}
type FileUser struct {
Id int `yaml:"id"`
}
type FileGroup struct {
Id int `yaml:"id"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, file := range in.Storage.Files {
newFile := ignTypes.File{
Filesystem: file.Filesystem,
Path: ignTypes.Path(file.Path),
Mode: ignTypes.FileMode(file.Mode),
User: ignTypes.FileUser{Id: file.User.Id},
Group: ignTypes.FileGroup{Id: file.Group.Id},
}
if file.Contents.Inline != "" {
newFile.Contents = ignTypes.FileContents{
Source: ignTypes.Url{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(file.Contents.Inline),
},
}
}
if file.Contents.Remote.Url != "" {
source, err := url.Parse(file.Contents.Remote.Url)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
}
newFile.Contents = ignTypes.FileContents{Source: ignTypes.Url(*source)}
}
if newFile.Contents == (ignTypes.FileContents{}) {
newFile.Contents = ignTypes.FileContents{
Source: ignTypes.Url{
Scheme: "data",
Opaque: ",",
},
}
}
newFile.Contents.Compression = ignTypes.Compression(file.Contents.Remote.Compression)
newFile.Contents.Verification = convertVerification(file.Contents.Remote.Verification)
out.Storage.Files = append(out.Storage.Files, newFile)
}
return out, report.Report{}
})
}

View File

@@ -0,0 +1,71 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Filesystem struct {
Name string `yaml:"name"`
Mount *Mount `yaml:"mount"`
Path string `yaml:"path"`
}
type Mount struct {
Device string `yaml:"device"`
Format string `yaml:"format"`
Create *Create `yaml:"create"`
}
type Create struct {
Force bool `yaml:"force"`
Options []string `yaml:"options"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, filesystem := range in.Storage.Filesystems {
newFilesystem := ignTypes.Filesystem{
Name: filesystem.Name,
Path: func(p ignTypes.Path) *ignTypes.Path {
if p == "" {
return nil
}
return &p
}(ignTypes.Path(filesystem.Path)),
}
if filesystem.Mount != nil {
newFilesystem.Mount = &ignTypes.FilesystemMount{
Device: ignTypes.Path(filesystem.Mount.Device),
Format: ignTypes.FilesystemFormat(filesystem.Mount.Format),
}
if filesystem.Mount.Create != nil {
newFilesystem.Mount.Create = &ignTypes.FilesystemCreate{
Force: filesystem.Mount.Create.Force,
Options: ignTypes.MkfsOptions(filesystem.Mount.Create.Options),
}
}
}
out.Storage.Filesystems = append(out.Storage.Filesystems, newFilesystem)
}
return out, report.Report{}
})
}

View File

@@ -0,0 +1,157 @@
package types
import (
"errors"
"fmt"
"github.com/coreos/go-semver/semver"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFlannelTooOld = errors.New("invalid flannel version (too old)")
ErrFlannelMinorTooNew = errors.New("flannel minor version too new. Only options available in the previous minor version will be supported")
OldestFlannelVersion = *semver.New("0.5.0")
FlannelDefaultVersion = *semver.New("0.6.0")
)
type Flannel struct {
Version *FlannelVersion `yaml:"version"`
Options
}
type flannelCommon Flannel
type FlannelVersion semver.Version
func (v *FlannelVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
t := semver.Version(*v)
if err := unmarshal(&t); err != nil {
return err
}
*v = FlannelVersion(t)
return nil
}
func (fv FlannelVersion) Validate() report.Report {
v := semver.Version(fv)
switch {
case v.LessThan(OldestFlannelVersion):
return report.ReportFromError(ErrFlannelTooOld, report.EntryError)
case v.Major == 0 && fv.Minor > 7:
return report.ReportFromError(ErrFlannelMinorTooNew, report.EntryWarning)
}
return report.Report{}
}
func (fv FlannelVersion) String() string {
return semver.Version(fv).String()
}
func (flannel *Flannel) UnmarshalYAML(unmarshal func(interface{}) error) error {
t := flannelCommon(*flannel)
if err := unmarshal(&t); err != nil {
return err
}
*flannel = Flannel(t)
var v semver.Version
if flannel.Version == nil {
v = FlannelDefaultVersion
} else {
v = semver.Version(*flannel.Version)
}
if v.Major == 0 && v.Minor >= 7 {
o := Flannel0_7{}
if err := unmarshal(&o); err != nil {
return err
}
flannel.Options = o
} else if v.Major == 0 && v.Minor == 6 {
o := Flannel0_6{}
if err := unmarshal(&o); err != nil {
return err
}
flannel.Options = o
} else if v.Major == 0 && v.Minor == 5 {
o := Flannel0_5{}
if err := unmarshal(&o); err != nil {
return err
}
flannel.Options = o
}
return nil
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
if in.Flannel != nil {
contents, err := flannelContents(*in.Flannel, platform)
if err != nil {
return ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
}
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "flanneld.service",
Enable: true,
DropIns: []ignTypes.SystemdUnitDropIn{{
Name: "20-clct-flannel.conf",
Contents: contents,
}},
})
}
return out, report.Report{}
})
}
// flannelContents creates the string containing the systemd drop in for flannel
func flannelContents(flannel Flannel, platform string) (string, error) {
args := getCliArgs(flannel.Options)
vars := []string{fmt.Sprintf("FLANNEL_IMAGE_TAG=v%s", flannel.Version)}
return assembleUnit("/usr/lib/coreos/flannel-wrapper $FLANNEL_OPTS", args, vars, platform)
}
// Flannel0_7 represents flannel options for version 0.7.x. Don't embed Flannel0_6 because
// the yaml parser doesn't handle embedded structs
type Flannel0_7 struct {
EtcdUsername string `yaml:"etcd_username" cli:"etcd-username"`
EtcdPassword string `yaml:"etcd_password" cli:"etcd-password"`
EtcdEndpoints string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile string `yaml:"subnet_file" cli:"subnet-file"`
Iface string `yaml:"interface" cli:"iface"`
PublicIP string `yaml:"public_ip" cli:"public-ip"`
KubeSubnetMgr bool `yaml:"kube_subnet_mgr" cli:"kube-subnet-mgr"`
}
type Flannel0_6 struct {
EtcdUsername string `yaml:"etcd_username" cli:"etcd-username"`
EtcdPassword string `yaml:"etcd_password" cli:"etcd-password"`
EtcdEndpoints string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile string `yaml:"subnet_file" cli:"subnet-file"`
Iface string `yaml:"interface" cli:"iface"`
PublicIP string `yaml:"public_ip" cli:"public-ip"`
}
type Flannel0_5 struct {
EtcdEndpoints string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile string `yaml:"subnet_file" cli:"subnet-file"`
Iface string `yaml:"interface" cli:"iface"`
PublicIP string `yaml:"public_ip" cli:"public-ip"`
}

View File

@@ -0,0 +1,41 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"strings"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrUnknownStrategy = errors.New("unknown reboot strategy")
)
type Locksmith struct {
RebootStrategy RebootStrategy `yaml:"reboot_strategy"`
}
type RebootStrategy string
func (r RebootStrategy) Validate() report.Report {
switch strings.ToLower(string(r)) {
case "reboot", "etcd-lock", "off":
return report.Report{}
default:
return report.ReportFromError(ErrUnknownStrategy, report.EntryError)
}
}

View File

@@ -0,0 +1,41 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Networkd struct {
Units []NetworkdUnit `yaml:"units"`
}
type NetworkdUnit struct {
Name string `yaml:"name"`
Contents string `yaml:"contents"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, unit := range in.Networkd.Units {
out.Networkd.Units = append(out.Networkd.Units, ignTypes.NetworkdUnit{
Name: ignTypes.NetworkdUnitName(unit.Name),
Contents: unit.Contents,
})
}
return out, report.Report{}
})
}

View File

@@ -14,6 +14,11 @@
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Passwd struct {
Users []User `yaml:"users"`
Groups []Group `yaml:"groups"`
@@ -45,3 +50,42 @@ type Group struct {
PasswordHash string `yaml:"password_hash"`
System bool `yaml:"system"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, user := range in.Passwd.Users {
newUser := ignTypes.User{
Name: user.Name,
PasswordHash: user.PasswordHash,
SSHAuthorizedKeys: user.SSHAuthorizedKeys,
}
if user.Create != nil {
newUser.Create = &ignTypes.UserCreate{
Uid: user.Create.Uid,
GECOS: user.Create.GECOS,
Homedir: user.Create.Homedir,
NoCreateHome: user.Create.NoCreateHome,
PrimaryGroup: user.Create.PrimaryGroup,
Groups: user.Create.Groups,
NoUserGroup: user.Create.NoUserGroup,
System: user.Create.System,
NoLogInit: user.Create.NoLogInit,
Shell: user.Create.Shell,
}
}
out.Passwd.Users = append(out.Passwd.Users, newUser)
}
for _, group := range in.Passwd.Groups {
out.Passwd.Groups = append(out.Passwd.Groups, ignTypes.Group{
Name: group.Name,
Gid: group.Gid,
PasswordHash: group.PasswordHash,
System: group.System,
})
}
return out, report.Report{}
})
}

View File

@@ -0,0 +1,46 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Raid struct {
Name string `yaml:"name"`
Level string `yaml:"level"`
Devices []string `yaml:"devices"`
Spares int `yaml:"spares"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, array := range in.Storage.Arrays {
newArray := ignTypes.Raid{
Name: array.Name,
Level: array.Level,
Spares: array.Spares,
}
for _, device := range array.Devices {
newArray.Devices = append(newArray.Devices, ignTypes.Path(device))
}
out.Storage.Arrays = append(out.Storage.Arrays, newArray)
}
return out, report.Report{}
})
}

View File

@@ -0,0 +1,60 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
)
type Systemd struct {
Units []SystemdUnit `yaml:"units"`
}
type SystemdUnit struct {
Name string `yaml:"name"`
Enable bool `yaml:"enable"`
Mask bool `yaml:"mask"`
Contents string `yaml:"contents"`
DropIns []SystemdUnitDropIn `yaml:"dropins"`
}
type SystemdUnitDropIn struct {
Name string `yaml:"name"`
Contents string `yaml:"contents"`
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, unit := range in.Systemd.Units {
newUnit := ignTypes.SystemdUnit{
Name: ignTypes.SystemdUnitName(unit.Name),
Enable: unit.Enable,
Mask: unit.Mask,
Contents: unit.Contents,
}
for _, dropIn := range unit.DropIns {
newUnit.DropIns = append(newUnit.DropIns, ignTypes.SystemdUnitDropIn{
Name: ignTypes.SystemdUnitDropInName(dropIn.Name),
Contents: dropIn.Contents,
})
}
out.Systemd.Units = append(out.Systemd.Units, newUnit)
}
return out, report.Report{}
})
}

View File

@@ -0,0 +1,91 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"fmt"
"net/url"
"strings"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
var (
ErrUnknownGroup = errors.New("unknown update group")
)
type Update struct {
Group UpdateGroup `yaml:"group"`
Server UpdateServer `yaml:"server"`
}
type UpdateGroup string
type UpdateServer string
func (u Update) Validate() report.Report {
switch strings.ToLower(string(u.Group)) {
case "stable", "beta", "alpha":
return report.Report{}
default:
if u.Server == "" {
return report.ReportFromError(ErrUnknownGroup, report.EntryWarning)
}
return report.Report{}
}
}
func (s UpdateServer) Validate() report.Report {
_, err := url.Parse(string(s))
if err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
var contents string
if in.Update != nil {
if in.Update.Group != "" {
contents += fmt.Sprintf("GROUP=%s", strings.ToLower(string(in.Update.Group)))
}
if in.Update.Server != "" {
contents += fmt.Sprintf("\nSERVER=%s", in.Update.Server)
}
}
if in.Locksmith != nil {
if in.Locksmith.RebootStrategy != "" {
contents += fmt.Sprintf("\nREBOOT_STRATEGY=%s", strings.ToLower(string(in.Locksmith.RebootStrategy)))
}
}
if contents != "" {
out.Storage.Files = append(out.Storage.Files, ignTypes.File{
Filesystem: "root",
Path: "/etc/coreos/update.conf",
Mode: 0644,
Contents: ignTypes.FileContents{
Source: ignTypes.Url{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(contents),
},
},
})
}
return out, report.Report{}
})
}

View File

@@ -20,6 +20,8 @@ type Flannel struct {
EtcdCertFile string `yaml:"etcd_certfile" env:"FLANNELD_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLANNELD_ETCD_KEYFILE"`
EtcdPrefix string `yaml:"etcd_prefix" env:"FLANNELD_ETCD_PREFIX"`
EtcdUsername string `yaml:"etcd_username" env:"FLANNELD_ETCD_USERNAME"`
EtcdPassword string `yaml:"etcd_password" env:"FLANNELD_ETCD_PASSWORD"`
IPMasq string `yaml:"ip_masq" env:"FLANNELD_IP_MASQ"`
SubnetFile string `yaml:"subnet_file" env:"FLANNELD_SUBNET_FILE"`
Iface string `yaml:"interface" env:"FLANNELD_IFACE"`

View File

@@ -25,6 +25,8 @@ type Fleet struct {
EtcdKeyPrefix string `yaml:"etcd_key_prefix" env:"FLEET_ETCD_KEY_PREFIX"`
EtcdRequestTimeout float64 `yaml:"etcd_request_timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
EtcdUsername string `yaml:"etcd_username" env:"FLEET_ETCD_USERNAME"`
EtcdPassword string `yaml:"etcd_password" env:"FLEET_ETCD_PASSWORD"`
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
TokenLimit int `yaml:"token_limit" env:"FLEET_TOKEN_LIMIT"`

View File

@@ -19,6 +19,8 @@ type Locksmith struct {
EtcdCAFile string `yaml:"etcd_cafile" env:"LOCKSMITHD_ETCD_CAFILE"`
EtcdCertFile string `yaml:"etcd_certfile" env:"LOCKSMITHD_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" env:"LOCKSMITHD_ETCD_KEYFILE"`
EtcdUsername string `yaml:"etcd_username" env:"LOCKSMITHD_ETCD_USERNAME"`
EtcdPassword string `yaml:"etcd_password" env:"LOCKSMITHD_ETCD_PASSWORD"`
Group string `yaml:"group" env:"LOCKSMITHD_GROUP"`
RebootWindowStart string `yaml:"window_start" env:"REBOOT_WINDOW_START" valid:"^((?i:sun|mon|tue|wed|thu|fri|sat|sun) )?0*([0-9]|1[0-9]|2[0-3]):0*([0-9]|[1-5][0-9])$"`
RebootWindowLength string `yaml:"window_length" env:"REBOOT_WINDOW_LENGTH" valid:"^[-+]?([0-9]*(\\.[0-9]*)?[a-z]+)+$"`

View File

@@ -109,7 +109,6 @@ var (
oemConfigs = map[string]oemConfig{
"digitalocean": {
"from-digitalocean-metadata": "http://169.254.169.254/",
"convert-netconf": "digitalocean",
},
"ec2-compat": {
"from-ec2-metadata": "http://169.254.169.254/",
@@ -170,11 +169,10 @@ func main() {
switch flags.convertNetconf {
case "":
case "debian":
case "digitalocean":
case "packet":
case "vmware":
default:
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean, packet, vmware'\n", flags.convertNetconf)
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, packet, vmware'\n", flags.convertNetconf)
os.Exit(2)
}
@@ -256,8 +254,6 @@ func main() {
switch flags.convertNetconf {
case "debian":
ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig.([]byte))
case "digitalocean":
ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig.(digitalocean.Metadata))
case "packet":
ifaces, err = network.ProcessPacketNetconf(metadata.NetworkConfig.(packet.NetworkData))
case "vmware":

View File

@@ -1,281 +0,0 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"net/url"
"reflect"
"github.com/alecthomas/units"
fuzeTypes "github.com/coreos/fuze/config/types"
"github.com/coreos/ignition/config/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
const (
BYTES_PER_SECTOR = 512
)
func ConvertAs2_0_0(in fuzeTypes.Config) (types.Config, report.Report) {
out := types.Config{
Ignition: types.Ignition{
Version: types.IgnitionVersion{Major: 2, Minor: 0},
},
}
for _, ref := range in.Ignition.Config.Append {
newRef, err := convertConfigReference(ref)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
}
out.Ignition.Config.Append = append(out.Ignition.Config.Append, newRef)
}
if in.Ignition.Config.Replace != nil {
newRef, err := convertConfigReference(*in.Ignition.Config.Replace)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
}
out.Ignition.Config.Replace = &newRef
}
for _, disk := range in.Storage.Disks {
newDisk := types.Disk{
Device: types.Path(disk.Device),
WipeTable: disk.WipeTable,
}
for _, partition := range disk.Partitions {
size, err := convertPartitionDimension(partition.Size)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
}
start, err := convertPartitionDimension(partition.Start)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
}
newDisk.Partitions = append(newDisk.Partitions, types.Partition{
Label: types.PartitionLabel(partition.Label),
Number: partition.Number,
Size: size,
Start: start,
TypeGUID: types.PartitionTypeGUID(partition.TypeGUID),
})
}
out.Storage.Disks = append(out.Storage.Disks, newDisk)
}
for _, array := range in.Storage.Arrays {
newArray := types.Raid{
Name: array.Name,
Level: array.Level,
Spares: array.Spares,
}
for _, device := range array.Devices {
newArray.Devices = append(newArray.Devices, types.Path(device))
}
out.Storage.Arrays = append(out.Storage.Arrays, newArray)
}
for _, filesystem := range in.Storage.Filesystems {
newFilesystem := types.Filesystem{
Name: filesystem.Name,
Path: func(p types.Path) *types.Path {
if p == "" {
return nil
}
return &p
}(types.Path(filesystem.Path)),
}
if filesystem.Mount != nil {
newFilesystem.Mount = &types.FilesystemMount{
Device: types.Path(filesystem.Mount.Device),
Format: types.FilesystemFormat(filesystem.Mount.Format),
}
if filesystem.Mount.Create != nil {
newFilesystem.Mount.Create = &types.FilesystemCreate{
Force: filesystem.Mount.Create.Force,
Options: types.MkfsOptions(filesystem.Mount.Create.Options),
}
}
}
out.Storage.Filesystems = append(out.Storage.Filesystems, newFilesystem)
}
for _, file := range in.Storage.Files {
newFile := types.File{
Filesystem: file.Filesystem,
Path: types.Path(file.Path),
Mode: types.FileMode(file.Mode),
User: types.FileUser{Id: file.User.Id},
Group: types.FileGroup{Id: file.Group.Id},
}
if file.Contents.Inline != "" {
newFile.Contents = types.FileContents{
Source: types.Url{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(file.Contents.Inline),
},
}
}
if file.Contents.Remote.Url != "" {
source, err := url.Parse(file.Contents.Remote.Url)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
}
newFile.Contents = types.FileContents{Source: types.Url(*source)}
}
if newFile.Contents == (types.FileContents{}) {
newFile.Contents = types.FileContents{
Source: types.Url{
Scheme: "data",
Opaque: ",",
},
}
}
newFile.Contents.Compression = types.Compression(file.Contents.Remote.Compression)
newFile.Contents.Verification = convertVerification(file.Contents.Remote.Verification)
out.Storage.Files = append(out.Storage.Files, newFile)
}
for _, unit := range in.Systemd.Units {
newUnit := types.SystemdUnit{
Name: types.SystemdUnitName(unit.Name),
Enable: unit.Enable,
Mask: unit.Mask,
Contents: unit.Contents,
}
for _, dropIn := range unit.DropIns {
newUnit.DropIns = append(newUnit.DropIns, types.SystemdUnitDropIn{
Name: types.SystemdUnitDropInName(dropIn.Name),
Contents: dropIn.Contents,
})
}
out.Systemd.Units = append(out.Systemd.Units, newUnit)
}
for _, unit := range in.Networkd.Units {
out.Networkd.Units = append(out.Networkd.Units, types.NetworkdUnit{
Name: types.NetworkdUnitName(unit.Name),
Contents: unit.Contents,
})
}
for _, user := range in.Passwd.Users {
newUser := types.User{
Name: user.Name,
PasswordHash: user.PasswordHash,
SSHAuthorizedKeys: user.SSHAuthorizedKeys,
}
if user.Create != nil {
newUser.Create = &types.UserCreate{
Uid: user.Create.Uid,
GECOS: user.Create.GECOS,
Homedir: user.Create.Homedir,
NoCreateHome: user.Create.NoCreateHome,
PrimaryGroup: user.Create.PrimaryGroup,
Groups: user.Create.Groups,
NoUserGroup: user.Create.NoUserGroup,
System: user.Create.System,
NoLogInit: user.Create.NoLogInit,
Shell: user.Create.Shell,
}
}
out.Passwd.Users = append(out.Passwd.Users, newUser)
}
for _, group := range in.Passwd.Groups {
out.Passwd.Groups = append(out.Passwd.Groups, types.Group{
Name: group.Name,
Gid: group.Gid,
PasswordHash: group.PasswordHash,
System: group.System,
})
}
r := validate.ValidateWithoutSource(reflect.ValueOf(out))
if r.IsFatal() {
return types.Config{}, r
}
return out, r
}
func convertConfigReference(in fuzeTypes.ConfigReference) (types.ConfigReference, error) {
source, err := url.Parse(in.Source)
if err != nil {
return types.ConfigReference{}, err
}
return types.ConfigReference{
Source: types.Url(*source),
Verification: convertVerification(in.Verification),
}, nil
}
func convertVerification(in fuzeTypes.Verification) types.Verification {
if in.Hash.Function == "" || in.Hash.Sum == "" {
return types.Verification{}
}
return types.Verification{
&types.Hash{
Function: in.Hash.Function,
Sum: in.Hash.Sum,
},
}
}
func convertPartitionDimension(in string) (types.PartitionDimension, error) {
if in == "" {
return 0, nil
}
b, err := units.ParseBase2Bytes(in)
if err != nil {
return 0, err
}
if b < 0 {
return 0, fmt.Errorf("invalid dimension (negative): %q", in)
}
// Translate bytes into sectors
sectors := (b / BYTES_PER_SECTOR)
if b%BYTES_PER_SECTOR != 0 {
sectors++
}
return types.PartitionDimension(uint64(sectors)), nil
}

View File

@@ -1,37 +0,0 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type Config struct {
Ignition Ignition `yaml:"ignition"`
Storage Storage `yaml:"storage"`
Systemd Systemd `yaml:"systemd"`
Networkd Networkd `yaml:"networkd"`
Passwd Passwd `yaml:"passwd"`
}
type Ignition struct {
Config IgnitionConfig `yaml:"config"`
}
type IgnitionConfig struct {
Append []ConfigReference `yaml:"append"`
Replace *ConfigReference `yaml:"replace"`
}
type ConfigReference struct {
Source string `yaml:"source"`
Verification Verification `yaml:"verification"`
}

View File

@@ -1,43 +0,0 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type File struct {
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
Mode int `yaml:"mode"`
Contents FileContents `yaml:"contents"`
User FileUser `yaml:"user"`
Group FileGroup `yaml:"group"`
}
type FileContents struct {
Remote Remote `yaml:"remote"`
Inline string `yaml:"inline"`
}
type Remote struct {
Url string `yaml:"url"`
Compression string `yaml:"compression"`
Verification Verification `yaml:"verification"`
}
type FileUser struct {
Id int `yaml:"id"`
}
type FileGroup struct {
Id int `yaml:"id"`
}

View File

@@ -44,16 +44,36 @@ func splitOff(input *string, delim string) (val string) {
return val
}
func New(version string) *Version {
return Must(NewVersion(version))
}
func NewVersion(version string) (*Version, error) {
v := Version{}
v.Metadata = splitOff(&version, "+")
v.PreRelease = PreRelease(splitOff(&version, "-"))
if err := v.Set(version); err != nil {
return nil, err
}
return &v, nil
}
// Must is a helper for wrapping NewVersion and will panic if err is not nil.
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
}
// Set parses and updates v from the given version string. Implements flag.Value
func (v *Version) Set(version string) error {
metadata := splitOff(&version, "+")
preRelease := PreRelease(splitOff(&version, "-"))
dotParts := strings.SplitN(version, ".", 3)
if len(dotParts) != 3 {
return nil, errors.New(fmt.Sprintf("%s is not in dotted-tri format", version))
return fmt.Errorf("%s is not in dotted-tri format", version)
}
parsed := make([]int64, 3, 3)
@@ -62,22 +82,16 @@ func NewVersion(version string) (*Version, error) {
val, err := strconv.ParseInt(v, 10, 64)
parsed[i] = val
if err != nil {
return nil, err
return err
}
}
v.Metadata = metadata
v.PreRelease = preRelease
v.Major = parsed[0]
v.Minor = parsed[1]
v.Patch = parsed[2]
return &v, nil
}
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
return nil
}
func (v Version) String() string {
@@ -101,12 +115,7 @@ func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&data); err != nil {
return err
}
vv, err := NewVersion(data)
if err != nil {
return err
}
*v = *vv
return nil
return v.Set(data)
}
func (v Version) MarshalJSON() ([]byte, error) {
@@ -121,30 +130,29 @@ func (v *Version) UnmarshalJSON(data []byte) error {
if l < 2 || data[0] != '"' || data[l-1] != '"' {
return errors.New("invalid semver string")
}
vv, err := NewVersion(string(data[1 : l-1]))
if err != nil {
return err
}
*v = *vv
return nil
return v.Set(string(data[1 : l-1]))
}
// Compare tests if v is less than, equal to, or greater than versionB,
// returning -1, 0, or +1 respectively.
func (v Version) Compare(versionB Version) int {
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
return cmp
}
return preReleaseCompare(v, versionB)
}
// Equal tests if v is equal to versionB.
func (v Version) Equal(versionB Version) bool {
return v.Compare(versionB) == 0
}
// LessThan tests if v is less than versionB.
func (v Version) LessThan(versionB Version) bool {
versionA := v
cmp := recursiveCompare(versionA.Slice(), versionB.Slice())
if cmp == 0 {
cmp = preReleaseCompare(versionA, versionB)
}
if cmp == -1 {
return true
}
return false
return v.Compare(versionB) < 0
}
/* Slice converts the comparable parts of the semver into a slice of strings */
// Slice converts the comparable parts of the semver into a slice of integers.
func (v Version) Slice() []int64 {
return []int64{v.Major, v.Minor, v.Patch}
}
@@ -166,7 +174,7 @@ func preReleaseCompare(versionA Version, versionB Version) int {
return -1
}
// If there is a prelease, check and compare each part.
// If there is a prerelease, check and compare each part.
return recursivePreReleaseCompare(a.Slice(), b.Slice())
}
@@ -188,9 +196,12 @@ func recursiveCompare(versionA []int64, versionB []int64) int {
}
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
// Handle slice length disparity.
// A larger set of pre-release fields has a higher precedence than a smaller set,
// if all of the preceding identifiers are equal.
if len(versionA) == 0 {
// Nothing to compare too, so we return 0
if len(versionB) > 0 {
return -1
}
return 0
} else if len(versionB) == 0 {
// We're longer than versionB so return 1.
@@ -213,6 +224,13 @@ func recursivePreReleaseCompare(versionA []string, versionB []string) int {
bInt = true
}
// Numeric identifiers always have lower precedence than non-numeric identifiers.
if aInt && !bInt {
return -1
} else if !aInt && bInt {
return 1
}
// Handle Integer Comparison
if aInt && bInt {
if aI > bI {

276
vendor/github.com/coreos/go-systemd/unit/deserialize.go generated vendored Normal file
View File

@@ -0,0 +1,276 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package unit
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"unicode"
)
const (
// SYSTEMD_LINE_MAX mimics the maximum line length that systemd can use.
// On typical systemd platforms (i.e. modern Linux), this will most
// commonly be 2048, so let's use that as a sanity check.
// Technically, we should probably pull this at runtime:
// SYSTEMD_LINE_MAX = int(C.sysconf(C.__SC_LINE_MAX))
// but this would introduce an (unfortunate) dependency on cgo
SYSTEMD_LINE_MAX = 2048
// characters that systemd considers indicate a newline
SYSTEMD_NEWLINE = "\r\n"
)
var (
ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX)
)
// Deserialize parses a systemd unit file into a list of UnitOption objects.
func Deserialize(f io.Reader) (opts []*UnitOption, err error) {
lexer, optchan, errchan := newLexer(f)
go lexer.lex()
for opt := range optchan {
opts = append(opts, &(*opt))
}
err = <-errchan
return opts, err
}
func newLexer(f io.Reader) (*lexer, <-chan *UnitOption, <-chan error) {
optchan := make(chan *UnitOption)
errchan := make(chan error, 1)
buf := bufio.NewReader(f)
return &lexer{buf, optchan, errchan, ""}, optchan, errchan
}
type lexer struct {
buf *bufio.Reader
optchan chan *UnitOption
errchan chan error
section string
}
func (l *lexer) lex() {
var err error
defer func() {
close(l.optchan)
close(l.errchan)
}()
next := l.lexNextSection
for next != nil {
if l.buf.Buffered() >= SYSTEMD_LINE_MAX {
// systemd truncates lines longer than LINE_MAX
// https://bugs.freedesktop.org/show_bug.cgi?id=85308
// Rather than allowing this to pass silently, let's
// explicitly gate people from encountering this
line, err := l.buf.Peek(SYSTEMD_LINE_MAX)
if err != nil {
l.errchan <- err
return
}
if bytes.IndexAny(line, SYSTEMD_NEWLINE) == -1 {
l.errchan <- ErrLineTooLong
return
}
}
next, err = next()
if err != nil {
l.errchan <- err
return
}
}
}
type lexStep func() (lexStep, error)
func (l *lexer) lexSectionName() (lexStep, error) {
sec, err := l.buf.ReadBytes(']')
if err != nil {
return nil, errors.New("unable to find end of section")
}
return l.lexSectionSuffixFunc(string(sec[:len(sec)-1])), nil
}
func (l *lexer) lexSectionSuffixFunc(section string) lexStep {
return func() (lexStep, error) {
garbage, _, err := l.toEOL()
if err != nil {
return nil, err
}
garbage = bytes.TrimSpace(garbage)
if len(garbage) > 0 {
return nil, fmt.Errorf("found garbage after section name %s: %v", l.section, garbage)
}
return l.lexNextSectionOrOptionFunc(section), nil
}
}
func (l *lexer) ignoreLineFunc(next lexStep) lexStep {
return func() (lexStep, error) {
for {
line, _, err := l.toEOL()
if err != nil {
return nil, err
}
line = bytes.TrimSuffix(line, []byte{' '})
// lack of continuation means this line has been exhausted
if !bytes.HasSuffix(line, []byte{'\\'}) {
break
}
}
// reached end of buffer, safe to exit
return next, nil
}
}
func (l *lexer) lexNextSection() (lexStep, error) {
r, _, err := l.buf.ReadRune()
if err != nil {
if err == io.EOF {
err = nil
}
return nil, err
}
if r == '[' {
return l.lexSectionName, nil
} else if isComment(r) {
return l.ignoreLineFunc(l.lexNextSection), nil
}
return l.lexNextSection, nil
}
func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep {
return func() (lexStep, error) {
r, _, err := l.buf.ReadRune()
if err != nil {
if err == io.EOF {
err = nil
}
return nil, err
}
if unicode.IsSpace(r) {
return l.lexNextSectionOrOptionFunc(section), nil
} else if r == '[' {
return l.lexSectionName, nil
} else if isComment(r) {
return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil
}
l.buf.UnreadRune()
return l.lexOptionNameFunc(section), nil
}
}
func (l *lexer) lexOptionNameFunc(section string) lexStep {
return func() (lexStep, error) {
var partial bytes.Buffer
for {
r, _, err := l.buf.ReadRune()
if err != nil {
return nil, err
}
if r == '\n' || r == '\r' {
return nil, errors.New("unexpected newline encountered while parsing option name")
}
if r == '=' {
break
}
partial.WriteRune(r)
}
name := strings.TrimSpace(partial.String())
return l.lexOptionValueFunc(section, name, bytes.Buffer{}), nil
}
}
func (l *lexer) lexOptionValueFunc(section, name string, partial bytes.Buffer) lexStep {
return func() (lexStep, error) {
for {
line, eof, err := l.toEOL()
if err != nil {
return nil, err
}
if len(bytes.TrimSpace(line)) == 0 {
break
}
partial.Write(line)
// lack of continuation means this value has been exhausted
idx := bytes.LastIndex(line, []byte{'\\'})
if idx == -1 || idx != (len(line)-1) {
break
}
if !eof {
partial.WriteRune('\n')
}
return l.lexOptionValueFunc(section, name, partial), nil
}
val := partial.String()
if strings.HasSuffix(val, "\n") {
// A newline was added to the end, so the file didn't end with a backslash.
// => Keep the newline
val = strings.TrimSpace(val) + "\n"
} else {
val = strings.TrimSpace(val)
}
l.optchan <- &UnitOption{Section: section, Name: name, Value: val}
return l.lexNextSectionOrOptionFunc(section), nil
}
}
// toEOL reads until the end-of-line or end-of-file.
// Returns (data, EOFfound, error)
func (l *lexer) toEOL() ([]byte, bool, error) {
line, err := l.buf.ReadBytes('\n')
// ignore EOF here since it's roughly equivalent to EOL
if err != nil && err != io.EOF {
return nil, false, err
}
line = bytes.TrimSuffix(line, []byte{'\r'})
line = bytes.TrimSuffix(line, []byte{'\n'})
return line, err == io.EOF, nil
}
func isComment(r rune) bool {
return r == '#' || r == ';'
}

116
vendor/github.com/coreos/go-systemd/unit/escape.go generated vendored Normal file
View File

@@ -0,0 +1,116 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Implements systemd-escape [--unescape] [--path]
package unit
import (
"fmt"
"strconv"
"strings"
)
const (
allowed = `:_.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`
)
// If isPath is true:
// We remove redundant '/'s, the leading '/', and trailing '/'.
// If the result is empty, a '/' is inserted.
//
// We always:
// Replace the following characters with `\x%x`:
// Leading `.`
// `-`, `\`, and anything not in this set: `:-_.\[0-9a-zA-Z]`
// Replace '/' with '-'.
func escape(unescaped string, isPath bool) string {
e := []byte{}
inSlashes := false
start := true
for i := 0; i < len(unescaped); i++ {
c := unescaped[i]
if isPath {
if c == '/' {
inSlashes = true
continue
} else if inSlashes {
inSlashes = false
if !start {
e = append(e, '-')
}
}
}
if c == '/' {
e = append(e, '-')
} else if start && c == '.' || strings.IndexByte(allowed, c) == -1 {
e = append(e, []byte(fmt.Sprintf(`\x%x`, c))...)
} else {
e = append(e, c)
}
start = false
}
if isPath && len(e) == 0 {
e = append(e, '-')
}
return string(e)
}
// If isPath is true:
// We always return a string beginning with '/'.
//
// We always:
// Replace '-' with '/'.
// Replace `\x%x` with the value represented in hex.
func unescape(escaped string, isPath bool) string {
u := []byte{}
for i := 0; i < len(escaped); i++ {
c := escaped[i]
if c == '-' {
c = '/'
} else if c == '\\' && len(escaped)-i >= 4 && escaped[i+1] == 'x' {
n, err := strconv.ParseInt(escaped[i+2:i+4], 16, 8)
if err == nil {
c = byte(n)
i += 3
}
}
u = append(u, c)
}
if isPath && (len(u) == 0 || u[0] != '/') {
u = append([]byte("/"), u...)
}
return string(u)
}
// UnitNameEscape escapes a string as `systemd-escape` would
func UnitNameEscape(unescaped string) string {
return escape(unescaped, false)
}
// UnitNameUnescape unescapes a string as `systemd-escape --unescape` would
func UnitNameUnescape(escaped string) string {
return unescape(escaped, false)
}
// UnitNamePathEscape escapes a string as `systemd-escape --path` would
func UnitNamePathEscape(unescaped string) string {
return escape(unescaped, true)
}
// UnitNamePathUnescape unescapes a string as `systemd-escape --path --unescape` would
func UnitNamePathUnescape(escaped string) string {
return unescape(escaped, true)
}

54
vendor/github.com/coreos/go-systemd/unit/option.go generated vendored Normal file
View File

@@ -0,0 +1,54 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package unit
import (
"fmt"
)
type UnitOption struct {
Section string
Name string
Value string
}
func NewUnitOption(section, name, value string) *UnitOption {
return &UnitOption{Section: section, Name: name, Value: value}
}
func (uo *UnitOption) String() string {
return fmt.Sprintf("{Section: %q, Name: %q, Value: %q}", uo.Section, uo.Name, uo.Value)
}
func (uo *UnitOption) Match(other *UnitOption) bool {
return uo.Section == other.Section &&
uo.Name == other.Name &&
uo.Value == other.Value
}
func AllMatch(u1 []*UnitOption, u2 []*UnitOption) bool {
length := len(u1)
if length != len(u2) {
return false
}
for i := 0; i < length; i++ {
if !u1[i].Match(u2[i]) {
return false
}
}
return true
}

75
vendor/github.com/coreos/go-systemd/unit/serialize.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package unit
import (
"bytes"
"io"
)
// Serialize encodes all of the given UnitOption objects into a
// unit file. When serialized the options are sorted in their
// supplied order but grouped by section.
func Serialize(opts []*UnitOption) io.Reader {
var buf bytes.Buffer
if len(opts) == 0 {
return &buf
}
// Index of sections -> ordered options
idx := map[string][]*UnitOption{}
// Separately preserve order in which sections were seen
sections := []string{}
for _, opt := range opts {
sec := opt.Section
if _, ok := idx[sec]; !ok {
sections = append(sections, sec)
}
idx[sec] = append(idx[sec], opt)
}
for i, sect := range sections {
writeSectionHeader(&buf, sect)
writeNewline(&buf)
opts := idx[sect]
for _, opt := range opts {
writeOption(&buf, opt)
writeNewline(&buf)
}
if i < len(sections)-1 {
writeNewline(&buf)
}
}
return &buf
}
func writeNewline(buf *bytes.Buffer) {
buf.WriteRune('\n')
}
func writeSectionHeader(buf *bytes.Buffer, section string) {
buf.WriteRune('[')
buf.WriteString(section)
buf.WriteRune(']')
}
func writeOption(buf *bytes.Buffer, opt *UnitOption) {
buf.WriteString(opt.Name)
buf.WriteRune('=')
buf.WriteString(opt.Value)
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/coreos/ignition/config/types"
"github.com/coreos/ignition/config/v1"
"github.com/coreos/ignition/config/v2_0"
"github.com/coreos/ignition/config/validate"
astjson "github.com/coreos/ignition/config/validate/astjson"
"github.com/coreos/ignition/config/validate/report"
@@ -40,14 +41,16 @@ var (
// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
switch majorVersion(rawConfig) {
case 1:
switch version(rawConfig) {
case types.IgnitionVersion{Major: 1}:
config, err := ParseFromV1(rawConfig)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError), err
}
return config, report.ReportFromError(ErrDeprecated, report.EntryDeprecated), nil
case types.IgnitionVersion{Major: 2, Minor: 0}:
return ParseFromV2_0(rawConfig)
default:
return ParseFromLatest(rawConfig)
}
@@ -136,10 +139,19 @@ func ParseFromV1(rawConfig []byte) (types.Config, error) {
return types.Config{}, err
}
return TranslateFromV1(config)
return TranslateFromV1(config), nil
}
func majorVersion(rawConfig []byte) int64 {
func ParseFromV2_0(rawConfig []byte) (types.Config, report.Report, error) {
cfg, report, err := v2_0.Parse(rawConfig)
if err != nil {
return types.Config{}, report, err
}
return TranslateFromV2_0(cfg), report, err
}
func version(rawConfig []byte) types.IgnitionVersion {
var composite struct {
Version *int `json:"ignitionVersion"`
Ignition struct {
@@ -147,18 +159,15 @@ func majorVersion(rawConfig []byte) int64 {
} `json:"ignition"`
}
if json.Unmarshal(rawConfig, &composite) != nil {
return 0
if json.Unmarshal(rawConfig, &composite) == nil {
if composite.Ignition.Version != nil {
return *composite.Ignition.Version
} else if composite.Version != nil {
return types.IgnitionVersion{Major: int64(*composite.Version)}
}
}
var major int64
if composite.Ignition.Version != nil {
major = composite.Ignition.Version.Major
} else if composite.Version != nil {
major = int64(*composite.Version)
}
return major
return types.IgnitionVersion{}
}
func isEmpty(userdata []byte) bool {

View File

@@ -19,14 +19,15 @@ import (
"github.com/coreos/ignition/config/types"
v1 "github.com/coreos/ignition/config/v1/types"
v2_0 "github.com/coreos/ignition/config/v2_0/types"
"github.com/vincent-petithory/dataurl"
)
func TranslateFromV1(old v1.Config) (types.Config, error) {
func TranslateFromV1(old v1.Config) types.Config {
config := types.Config{
Ignition: types.Ignition{
Version: types.IgnitionVersion{Major: 2},
Version: types.IgnitionVersion(v2_0.MaxVersion),
},
}
@@ -83,17 +84,19 @@ func TranslateFromV1(old v1.Config) (types.Config, error) {
for _, oldFile := range oldFilesystem.Files {
file := types.File{
Filesystem: filesystem.Name,
Path: types.Path(oldFile.Path),
Node: types.Node{
Filesystem: filesystem.Name,
Path: types.Path(oldFile.Path),
Mode: types.NodeMode(oldFile.Mode),
User: types.NodeUser{Id: oldFile.Uid},
Group: types.NodeGroup{Id: oldFile.Gid},
},
Contents: types.FileContents{
Source: types.Url{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(oldFile.Contents),
},
},
Mode: types.FileMode(oldFile.Mode),
User: types.FileUser{Id: oldFile.Uid},
Group: types.FileGroup{Id: oldFile.Gid},
}
config.Storage.Files = append(config.Storage.Files, file)
@@ -159,5 +162,178 @@ func TranslateFromV1(old v1.Config) (types.Config, error) {
})
}
return config, nil
return config
}
func TranslateFromV2_0(old v2_0.Config) types.Config {
translateVerification := func(old v2_0.Verification) types.Verification {
var ver types.Verification
if old.Hash != nil {
h := types.Hash(*old.Hash)
ver.Hash = &h
}
return ver
}
translateConfigReference := func(old v2_0.ConfigReference) types.ConfigReference {
return types.ConfigReference{
Source: types.Url(old.Source),
Verification: translateVerification(old.Verification),
}
}
config := types.Config{
Ignition: types.Ignition{
Version: types.IgnitionVersion(types.MaxVersion),
},
}
if old.Ignition.Config.Replace != nil {
ref := translateConfigReference(*old.Ignition.Config.Replace)
config.Ignition.Config.Replace = &ref
}
for _, oldAppend := range old.Ignition.Config.Append {
config.Ignition.Config.Append =
append(config.Ignition.Config.Append, translateConfigReference(oldAppend))
}
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: types.Path(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: types.PartitionLabel(oldPartition.Label),
Number: oldPartition.Number,
Size: types.PartitionDimension(oldPartition.Size),
Start: types.PartitionDimension(oldPartition.Start),
TypeGUID: types.PartitionTypeGUID(oldPartition.TypeGUID),
})
}
config.Storage.Disks = append(config.Storage.Disks, disk)
}
for _, oldArray := range old.Storage.Arrays {
array := types.Raid{
Name: oldArray.Name,
Level: oldArray.Level,
Spares: oldArray.Spares,
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Path(oldDevice))
}
config.Storage.Arrays = append(config.Storage.Arrays, array)
}
for _, oldFilesystem := range old.Storage.Filesystems {
filesystem := types.Filesystem{
Name: oldFilesystem.Name,
}
if oldFilesystem.Mount != nil {
filesystem.Mount = &types.FilesystemMount{
Device: types.Path(oldFilesystem.Mount.Device),
Format: types.FilesystemFormat(oldFilesystem.Mount.Format),
}
if oldFilesystem.Mount.Create != nil {
filesystem.Mount.Create = &types.FilesystemCreate{
Force: oldFilesystem.Mount.Create.Force,
Options: types.MkfsOptions(oldFilesystem.Mount.Create.Options),
}
}
}
if oldFilesystem.Path != nil {
path := types.Path(*oldFilesystem.Path)
filesystem.Path = &path
}
config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)
}
for _, oldFile := range old.Storage.Files {
file := types.File{
Node: types.Node{
Filesystem: oldFile.Filesystem,
Path: types.Path(oldFile.Path),
Mode: types.NodeMode(oldFile.Mode),
User: types.NodeUser{Id: oldFile.User.Id},
Group: types.NodeGroup{Id: oldFile.Group.Id},
},
Contents: types.FileContents{
Compression: types.Compression(oldFile.Contents.Compression),
Source: types.Url(oldFile.Contents.Source),
Verification: translateVerification(oldFile.Contents.Verification),
},
}
config.Storage.Files = append(config.Storage.Files, file)
}
for _, oldUnit := range old.Systemd.Units {
unit := types.SystemdUnit{
Name: types.SystemdUnitName(oldUnit.Name),
Enable: oldUnit.Enable,
Mask: oldUnit.Mask,
Contents: oldUnit.Contents,
}
for _, oldDropIn := range oldUnit.DropIns {
unit.DropIns = append(unit.DropIns, types.SystemdUnitDropIn{
Name: types.SystemdUnitDropInName(oldDropIn.Name),
Contents: oldDropIn.Contents,
})
}
config.Systemd.Units = append(config.Systemd.Units, unit)
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.NetworkdUnit{
Name: types.NetworkdUnitName(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.User{
Name: oldUser.Name,
PasswordHash: oldUser.PasswordHash,
SSHAuthorizedKeys: oldUser.SSHAuthorizedKeys,
}
if oldUser.Create != nil {
user.Create = &types.UserCreate{
Uid: oldUser.Create.Uid,
GECOS: oldUser.Create.GECOS,
Homedir: oldUser.Create.Homedir,
NoCreateHome: oldUser.Create.NoCreateHome,
PrimaryGroup: oldUser.Create.PrimaryGroup,
Groups: oldUser.Create.Groups,
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
Shell: oldUser.Create.Shell,
}
}
config.Passwd.Users = append(config.Passwd.Users, user)
}
for _, oldGroup := range old.Passwd.Groups {
config.Passwd.Groups = append(config.Passwd.Groups, types.Group{
Name: oldGroup.Name,
Gid: oldGroup.Gid,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
}
return config
}

View File

@@ -24,8 +24,9 @@ import (
var (
MaxVersion = semver.Version{
Major: 2,
Minor: 0,
Major: 2,
Minor: 1,
PreRelease: "experimental",
}
)

View File

@@ -14,19 +14,16 @@
package types
type Filesystem struct {
Name string `yaml:"name"`
Mount *Mount `yaml:"mount"`
Path string `yaml:"path"`
}
import (
"path"
)
type Mount struct {
Device string `yaml:"device"`
Format string `yaml:"format"`
Create *Create `yaml:"create"`
}
type Directory Node
type Create struct {
Force bool `yaml:"force"`
Options []string `yaml:"options"`
func (d *Directory) Depth() int {
count := 0
for p := path.Clean(string(d.Path)); p != "/"; count++ {
p = path.Dir(p)
}
return count
}

View File

@@ -14,40 +14,10 @@
package types
import (
"errors"
"os"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
ErrNoFilesystem = errors.New("no filesystem specified")
)
// File represents regular files
type File struct {
Filesystem string `json:"filesystem,omitempty"`
Path Path `json:"path,omitempty"`
Contents FileContents `json:"contents,omitempty"`
Mode FileMode `json:"mode,omitempty"`
User FileUser `json:"user,omitempty"`
Group FileGroup `json:"group,omitempty"`
}
func (f File) Validate() report.Report {
if f.Filesystem == "" {
return report.ReportFromError(ErrNoFilesystem, report.EntryError)
}
return report.Report{}
}
type FileUser struct {
Id int `json:"id,omitempty"`
}
type FileGroup struct {
Id int `json:"id,omitempty"`
Node
Contents FileContents `json:"contents,omitempty"`
}
type FileContents struct {
@@ -55,12 +25,3 @@ type FileContents struct {
Source Url `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type FileMode os.FileMode
func (m FileMode) Validate() report.Report {
if (m &^ 07777) != 0 {
return report.ReportFromError(ErrFileIllegalMode, report.EntryError)
}
return report.Report{}
}

View File

@@ -29,8 +29,9 @@ var (
)
type Ignition struct {
Version IgnitionVersion `json:"version,omitempty" merge:"old"`
Config IgnitionConfig `json:"config,omitempty" merge:"new"`
Version IgnitionVersion `json:"version,omitempty" merge:"old"`
Config IgnitionConfig `json:"config,omitempty" merge:"new"`
Timeouts Timeouts `json:"timeouts,omitempty" merge:"new"`
}
type IgnitionConfig struct {

60
vendor/github.com/coreos/ignition/config/types/node.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"os"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrNoFilesystem = errors.New("no filesystem specified")
ErrFileIllegalMode = errors.New("illegal file mode")
)
// Node represents all common info for files (special types, e.g. directories, included).
type Node struct {
Filesystem string `json:"filesystem,omitempty"`
Path Path `json:"path,omitempty"`
Mode NodeMode `json:"mode,omitempty"`
User NodeUser `json:"user,omitempty"`
Group NodeGroup `json:"group,omitempty"`
}
type NodeUser struct {
Id int `json:"id,omitempty"`
}
type NodeGroup struct {
Id int `json:"id,omitempty"`
}
func (n Node) Validate() report.Report {
if n.Filesystem == "" {
return report.ReportFromError(ErrNoFilesystem, report.EntryError)
}
return report.Report{}
}
type NodeMode os.FileMode
func (m NodeMode) Validate() report.Report {
if (m &^ 07777) != 0 {
return report.ReportFromError(ErrFileIllegalMode, report.EntryError)
}
return report.Report{}
}

View File

@@ -16,7 +16,7 @@ package types
import (
"errors"
"path/filepath"
"path"
"github.com/coreos/ignition/config/validate/report"
)
@@ -32,7 +32,7 @@ func (p Path) MarshalJSON() ([]byte, error) {
}
func (p Path) Validate() report.Report {
if !filepath.IsAbs(string(p)) {
if !path.IsAbs(string(p)) {
return report.ReportFromError(ErrPathRelative, report.EntryError)
}
return report.Report{}

View File

@@ -19,4 +19,5 @@ type Storage struct {
Arrays []Raid `json:"raid,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Files []File `json:"files,omitempty"`
Directories []Directory `json:"directories,omitempty"`
}

View File

@@ -14,9 +14,7 @@
package types
type Raid struct {
Name string `yaml:"name"`
Level string `yaml:"level"`
Devices []string `yaml:"devices"`
Spares int `yaml:"spares"`
type Timeouts struct {
HttpResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
HttpTotal *int `json:"httpTotal,omitempty"`
}

View File

@@ -15,8 +15,12 @@
package types
import (
"bytes"
"errors"
"path/filepath"
"fmt"
"path"
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/validate/report"
)
@@ -29,15 +33,31 @@ type SystemdUnit struct {
DropIns []SystemdUnitDropIn `json:"dropins,omitempty"`
}
func (u SystemdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type SystemdUnitDropIn struct {
Name SystemdUnitDropInName `json:"name,omitempty"`
Contents string `json:"contents,omitempty"`
}
func (u SystemdUnitDropIn) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type SystemdUnitName string
func (n SystemdUnitName) Validate() report.Report {
switch filepath.Ext(string(n)) {
switch path.Ext(string(n)) {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
return report.Report{}
default:
@@ -48,7 +68,7 @@ func (n SystemdUnitName) Validate() report.Report {
type SystemdUnitDropInName string
func (n SystemdUnitDropInName) Validate() report.Report {
switch filepath.Ext(string(n)) {
switch path.Ext(string(n)) {
case ".conf":
return report.Report{}
default:
@@ -61,13 +81,31 @@ type NetworkdUnit struct {
Contents string `json:"contents,omitempty"`
}
func (u NetworkdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type NetworkdUnitName string
func (n NetworkdUnitName) Validate() report.Report {
switch filepath.Ext(string(n)) {
switch path.Ext(string(n)) {
case ".link", ".netdev", ".network":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid networkd unit extension"), report.EntryError)
}
}
func validateUnitContent(content string) error {
c := bytes.NewBufferString(content)
_, err := unit.Deserialize(c)
if err != nil {
return fmt.Errorf("invalid unit content: %s", err)
}
return nil
}

View File

@@ -20,6 +20,7 @@ import (
"net/url"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
var (
@@ -58,9 +59,14 @@ func (u Url) Validate() report.Report {
return report.Report{}
}
switch url.URL(u).Scheme {
case "http", "https", "oem", "data":
case "http", "https", "oem":
return report.Report{}
case "data":
if _, err := dataurl.DecodeString(u.String()); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
default:
return report.ReportFromError(ErrInvalidScheme, report.EntryError)
}
return report.ReportFromError(ErrInvalidScheme, report.EntryError)
}

View File

@@ -17,7 +17,7 @@ package types
import (
"encoding/json"
"errors"
"path/filepath"
"path"
)
var (
@@ -25,19 +25,18 @@ var (
)
type Path string
type path Path
func (d *Path) UnmarshalJSON(data []byte) error {
td := path(*d)
if err := json.Unmarshal(data, &td); err != nil {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*d = Path(td)
*d = Path(s)
return d.AssertValid()
}
func (d Path) AssertValid() error {
if !filepath.IsAbs(string(d)) {
if !path.IsAbs(string(d)) {
return ErrPathRelative
}
return nil

View File

@@ -17,7 +17,7 @@ package types
import (
"encoding/json"
"errors"
"path/filepath"
"path"
)
type SystemdUnit struct {
@@ -46,7 +46,7 @@ func (n *SystemdUnitName) UnmarshalJSON(data []byte) error {
}
func (n SystemdUnitName) AssertValid() error {
switch filepath.Ext(string(n)) {
switch path.Ext(string(n)) {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
return nil
default:
@@ -67,7 +67,7 @@ func (n *SystemdUnitDropInName) UnmarshalJSON(data []byte) error {
}
func (n SystemdUnitDropInName) AssertValid() error {
switch filepath.Ext(string(n)) {
switch path.Ext(string(n)) {
case ".conf":
return nil
default:
@@ -93,7 +93,7 @@ func (n *NetworkdUnitName) UnmarshalJSON(data []byte) error {
}
func (n NetworkdUnitName) AssertValid() error {
switch filepath.Ext(string(n)) {
switch path.Ext(string(n)) {
case ".link", ".netdev", ".network":
return nil
default:

View File

@@ -0,0 +1,73 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v2_0
import (
"reflect"
"github.com/coreos/ignition/config/v2_0/types"
)
// Append appends newConfig to oldConfig and returns the result. Appending one
// config to another is accomplished by iterating over every field in the
// config structure, appending slices, recursively appending structs, and
// overwriting old values with new values for all other types.
func Append(oldConfig, newConfig types.Config) types.Config {
vOld := reflect.ValueOf(oldConfig)
vNew := reflect.ValueOf(newConfig)
vResult := appendStruct(vOld, vNew)
return vResult.Interface().(types.Config)
}
// appendStruct is an internal helper function to AppendConfig. Given two values
// of structures (assumed to be the same type), recursively iterate over every
// field in the struct, appending slices, recursively appending structs, and
// overwriting old values with the new for all other types. Individual fields
// are able to override their merge strategy using the "merge" tag. Accepted
// values are "new" or "old": "new" uses the new value, "old" uses the old
// value. These are currently only used for "ignition.config" and
// "ignition.version".
func appendStruct(vOld, vNew reflect.Value) reflect.Value {
tOld := vOld.Type()
vRes := reflect.New(tOld)
for i := 0; i < tOld.NumField(); i++ {
vfOld := vOld.Field(i)
vfNew := vNew.Field(i)
vfRes := vRes.Elem().Field(i)
switch tOld.Field(i).Tag.Get("merge") {
case "old":
vfRes.Set(vfOld)
continue
case "new":
vfRes.Set(vfNew)
continue
}
switch vfOld.Type().Kind() {
case reflect.Struct:
vfRes.Set(appendStruct(vfOld, vfNew))
case reflect.Slice:
vfRes.Set(reflect.AppendSlice(vfOld, vfNew))
default:
vfRes.Set(vfNew)
}
}
return vRes.Elem()
}

View File

@@ -0,0 +1,53 @@
// 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 v2_0
import (
"bytes"
"compress/gzip"
"io/ioutil"
"strings"
"unicode"
)
func isCloudConfig(userdata []byte) bool {
header := strings.SplitN(string(decompressIfGzipped(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(decompressIfGzipped(userdata)), "\n", 2)[0]
return strings.HasPrefix(header, "#!")
}
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
}
}

120
vendor/github.com/coreos/ignition/config/v2_0/config.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
// 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 v2_0
import (
"bytes"
"errors"
"reflect"
"github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
astjson "github.com/coreos/ignition/config/validate/astjson"
"github.com/coreos/ignition/config/validate/report"
json "github.com/ajeddeloh/go-json"
"go4.org/errorutil"
)
var (
ErrCloudConfig = errors.New("not a config (found coreos-cloudconfig)")
ErrEmpty = errors.New("not a config (empty)")
ErrScript = errors.New("not a config (found coreos-cloudinit script)")
ErrDeprecated = errors.New("config format deprecated")
ErrInvalid = errors.New("config is not valid")
)
// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, ErrScript
}
var err error
var config types.Config
// These errors are fatal and the config should not be further validated
if err = json.Unmarshal(rawConfig, &config); err == nil {
versionReport := config.Ignition.Version.Validate()
if versionReport.IsFatal() {
return types.Config{}, versionReport, ErrInvalid
}
}
// Handle json syntax and type errors first, since they are fatal but have offset info
if serr, ok := err.(*json.SyntaxError); ok {
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset)
return types.Config{},
report.Report{
Entries: []report.Entry{{
Kind: report.EntryError,
Message: serr.Error(),
Line: line,
Column: col,
Highlight: highlight,
}},
},
ErrInvalid
}
if terr, ok := err.(*json.UnmarshalTypeError); ok {
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset)
return types.Config{},
report.Report{
Entries: []report.Entry{{
Kind: report.EntryError,
Message: terr.Error(),
Line: line,
Column: col,
Highlight: highlight,
}},
},
ErrInvalid
}
// Handle other fatal errors (i.e. invalid version)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError), err
}
// Unmarshal again to a json.Node to get offset information for building a report
var ast json.Node
var r report.Report
configValue := reflect.ValueOf(config)
if err := json.Unmarshal(rawConfig, &ast); err != nil {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.",
})
r.Merge(validate.ValidateWithoutSource(configValue))
} else {
r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig)))
}
if r.IsFatal() {
return types.Config{}, r, ErrInvalid
}
return config, r, nil
}
func isEmpty(userdata []byte) bool {
return len(userdata) == 0
}

View File

@@ -0,0 +1,36 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrCompressionInvalid = errors.New("invalid compression method")
)
type Compression string
func (c Compression) Validate() report.Report {
switch c {
case "", "gzip":
default:
return report.ReportFromError(ErrCompressionInvalid, report.EntryError)
}
return report.Report{}
}

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 types
import (
"fmt"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/validate/report"
)
var (
MaxVersion = semver.Version{
Major: 2,
Minor: 0,
}
)
type Config struct {
Ignition Ignition `json:"ignition"`
Storage Storage `json:"storage,omitempty"`
Systemd Systemd `json:"systemd,omitempty"`
Networkd Networkd `json:"networkd,omitempty"`
Passwd Passwd `json:"passwd,omitempty"`
}
func (c Config) Validate() report.Report {
r := report.Report{}
rules := []rule{
checkFilesFilesystems,
checkDuplicateFilesystems,
}
for _, rule := range rules {
rule(c, &r)
}
return r
}
type rule func(cfg Config, report *report.Report)
func checkFilesFilesystems(cfg Config, r *report.Report) {
filesystems := map[string]struct{}{"root": {}}
for _, filesystem := range cfg.Storage.Filesystems {
filesystems[filesystem.Name] = struct{}{}
}
for _, file := range cfg.Storage.Files {
if file.Filesystem == "" {
// Filesystem was not specified. This is an error, but its handled in types.File's Validate, not here
continue
}
_, ok := filesystems[file.Filesystem]
if !ok {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: fmt.Sprintf("File %q references nonexistent filesystem %q. (This is ok if it is defined in a referenced config)",
file.Path, file.Filesystem),
})
}
}
}
func checkDuplicateFilesystems(cfg Config, r *report.Report) {
filesystems := map[string]struct{}{"root": {}}
for _, filesystem := range cfg.Storage.Filesystems {
if _, ok := filesystems[filesystem.Name]; ok {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: fmt.Sprintf("Filesystem %q shadows exising filesystem definition", filesystem.Name),
})
}
filesystems[filesystem.Name] = struct{}{}
}
}

View File

@@ -0,0 +1,124 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
type Disk struct {
Device Path `json:"device,omitempty"`
WipeTable bool `json:"wipeTable,omitempty"`
Partitions []Partition `json:"partitions,omitempty"`
}
func (n Disk) Validate() report.Report {
r := report.Report{}
if len(n.Device) == 0 {
r.Add(report.Entry{
Message: "disk device is required",
Kind: report.EntryError,
})
}
if n.partitionNumbersCollide() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device),
Kind: report.EntryError,
})
}
if n.partitionsOverlap() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions overlap", n.Device),
Kind: report.EntryError,
})
}
if n.partitionsMisaligned() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device),
Kind: report.EntryError,
})
}
// Disks which have no errors at this point will likely succeed in sgdisk
return r
}
// partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique.
func (n Disk) partitionNumbersCollide() bool {
m := map[int][]Partition{}
for _, p := range n.Partitions {
m[p.Number] = append(m[p.Number], p)
}
for _, n := range m {
if len(n) > 1 {
// TODO(vc): return information describing the collision for logging
return true
}
}
return false
}
// end returns the last sector of a partition.
func (p Partition) end() PartitionDimension {
if p.Size == 0 {
// a size of 0 means "fill available", just return the start as the end for those.
return p.Start
}
return p.Start + p.Size - 1
}
// partitionsOverlap returns true if any explicitly dimensioned partitions overlap
func (n Disk) partitionsOverlap() bool {
for _, p := range n.Partitions {
// Starts of 0 are placed by sgdisk into the "largest available block" at that time.
// We aren't going to check those for overlap since we don't have the disk geometry.
if p.Start == 0 {
continue
}
for _, o := range n.Partitions {
if p == o || o.Start == 0 {
continue
}
// is p.Start within o?
if p.Start >= o.Start && p.Start <= o.end() {
return true
}
// is p.end() within o?
if p.end() >= o.Start && p.end() <= o.end() {
return true
}
// do p.Start and p.end() straddle o?
if p.Start < o.Start && p.end() > o.end() {
return true
}
}
}
return false
}
// partitionsMisaligned returns true if any of the partitions don't start on a 2048-sector (1MiB) boundary.
func (n Disk) partitionsMisaligned() bool {
for _, p := range n.Partitions {
if (p.Start & (2048 - 1)) != 0 {
return true
}
}
return false
}

View File

@@ -0,0 +1,66 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"os"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
ErrNoFilesystem = errors.New("no filesystem specified")
)
type File struct {
Filesystem string `json:"filesystem,omitempty"`
Path Path `json:"path,omitempty"`
Contents FileContents `json:"contents,omitempty"`
Mode FileMode `json:"mode,omitempty"`
User FileUser `json:"user,omitempty"`
Group FileGroup `json:"group,omitempty"`
}
func (f File) Validate() report.Report {
if f.Filesystem == "" {
return report.ReportFromError(ErrNoFilesystem, report.EntryError)
}
return report.Report{}
}
type FileUser struct {
Id int `json:"id,omitempty"`
}
type FileGroup struct {
Id int `json:"id,omitempty"`
}
type FileContents struct {
Compression Compression `json:"compression,omitempty"`
Source Url `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type FileMode os.FileMode
func (m FileMode) Validate() report.Report {
if (m &^ 07777) != 0 {
return report.ReportFromError(ErrFileIllegalMode, report.EntryError)
}
return report.Report{}
}

View File

@@ -0,0 +1,67 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFilesystemInvalidFormat = errors.New("invalid filesystem format")
ErrFilesystemNoMountPath = errors.New("filesystem is missing mount or path")
ErrFilesystemMountAndPath = errors.New("filesystem has both mount and path defined")
)
type Filesystem struct {
Name string `json:"name,omitempty"`
Mount *FilesystemMount `json:"mount,omitempty"`
Path *Path `json:"path,omitempty"`
}
type FilesystemMount struct {
Device Path `json:"device,omitempty"`
Format FilesystemFormat `json:"format,omitempty"`
Create *FilesystemCreate `json:"create,omitempty"`
}
type FilesystemCreate struct {
Force bool `json:"force,omitempty"`
Options MkfsOptions `json:"options,omitempty"`
}
func (f Filesystem) Validate() report.Report {
if f.Mount == nil && f.Path == nil {
return report.ReportFromError(ErrFilesystemNoMountPath, report.EntryError)
}
if f.Mount != nil && f.Path != nil {
return report.ReportFromError(ErrFilesystemMountAndPath, report.EntryError)
}
return report.Report{}
}
type FilesystemFormat string
func (f FilesystemFormat) Validate() report.Report {
switch f {
case "ext4", "btrfs", "xfs":
return report.Report{}
default:
return report.ReportFromError(ErrFilesystemInvalidFormat, report.EntryError)
}
}
type MkfsOptions []string

View File

@@ -14,16 +14,9 @@
package types
type Disk struct {
Device string `yaml:"device"`
WipeTable bool `yaml:"wipe_table"`
Partitions []Partition `yaml:"partitions"`
}
type Partition struct {
Label string `yaml:"label"`
Number int `yaml:"number"`
Size string `yaml:"size"`
Start string `yaml:"start"`
TypeGUID string `yaml:"type_guid"`
type Group struct {
Name string `json:"name,omitempty"`
Gid *uint `json:"gid,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
System bool `json:"system,omitempty"`
}

View File

@@ -0,0 +1,73 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"crypto"
"encoding/hex"
"encoding/json"
"errors"
"strings"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrHashMalformed = errors.New("malformed hash specifier")
ErrHashWrongSize = errors.New("incorrect size for hash sum")
ErrHashUnrecognized = errors.New("unrecognized hash function")
)
type Hash struct {
Function string
Sum string
}
func (h *Hash) UnmarshalJSON(data []byte) error {
var th string
if err := json.Unmarshal(data, &th); err != nil {
return err
}
parts := strings.SplitN(th, "-", 2)
if len(parts) != 2 {
return ErrHashMalformed
}
h.Function = parts[0]
h.Sum = parts[1]
return nil
}
func (h Hash) MarshalJSON() ([]byte, error) {
return []byte(`"` + h.Function + "-" + h.Sum + `"`), nil
}
func (h Hash) Validate() report.Report {
var hash crypto.Hash
switch h.Function {
case "sha512":
hash = crypto.SHA512
default:
return report.ReportFromError(ErrHashUnrecognized, report.EntryError)
}
if len(h.Sum) != hex.EncodedLen(hash.Size()) {
return report.ReportFromError(ErrHashWrongSize, report.EntryError)
}
return report.Report{}
}

View File

@@ -0,0 +1,69 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"encoding/json"
"errors"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrOldVersion = errors.New("incorrect config version (too old)")
ErrNewVersion = errors.New("incorrect config version (too new)")
)
type Ignition struct {
Version IgnitionVersion `json:"version,omitempty" merge:"old"`
Config IgnitionConfig `json:"config,omitempty" merge:"new"`
}
type IgnitionConfig struct {
Append []ConfigReference `json:"append,omitempty"`
Replace *ConfigReference `json:"replace,omitempty"`
}
type ConfigReference struct {
Source Url `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type IgnitionVersion semver.Version
func (v *IgnitionVersion) UnmarshalJSON(data []byte) error {
tv := semver.Version(*v)
if err := json.Unmarshal(data, &tv); err != nil {
return err
}
*v = IgnitionVersion(tv)
return nil
}
func (v IgnitionVersion) MarshalJSON() ([]byte, error) {
return semver.Version(v).MarshalJSON()
}
func (v IgnitionVersion) Validate() report.Report {
if MaxVersion.Major > v.Major {
return report.ReportFromError(ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(semver.Version(v)) {
return report.ReportFromError(ErrNewVersion, report.EntryError)
}
return report.Report{}
}

View File

@@ -15,10 +15,5 @@
package types
type Networkd struct {
Units []NetworkdUnit `yaml:"units"`
}
type NetworkdUnit struct {
Name string `yaml:"name"`
Contents string `yaml:"contents"`
Units []NetworkdUnit `json:"units,omitempty"`
}

View File

@@ -0,0 +1,59 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"regexp"
"github.com/coreos/ignition/config/validate/report"
)
type Partition struct {
Label PartitionLabel `json:"label,omitempty"`
Number int `json:"number"`
Size PartitionDimension `json:"size"`
Start PartitionDimension `json:"start"`
TypeGUID PartitionTypeGUID `json:"typeGuid,omitempty"`
}
type PartitionLabel string
func (n PartitionLabel) Validate() report.Report {
// http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries:
// 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units)
// XXX(vc): note GPT calls it a name, we're using label for consistency
// with udev naming /dev/disk/by-partlabel/*.
if len(string(n)) > 36 {
return report.ReportFromError(fmt.Errorf("partition labels may not exceed 36 characters"), report.EntryError)
}
return report.Report{}
}
type PartitionDimension uint64
type PartitionTypeGUID string
func (d PartitionTypeGUID) Validate() report.Report {
ok, err := regexp.MatchString("^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$", string(d))
if err != nil {
return report.ReportFromError(fmt.Errorf("error matching type-guid regexp: %v", err), report.EntryError)
}
if !ok {
return report.ReportFromError(fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)), report.EntryError)
}
return report.Report{}
}

View File

@@ -0,0 +1,20 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type Passwd struct {
Users []User `json:"users,omitempty"`
Groups []Group `json:"groups,omitempty"`
}

View File

@@ -14,19 +14,26 @@
package types
type Systemd struct {
Units []SystemdUnit `yaml:"units"`
import (
"errors"
"path"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrPathRelative = errors.New("path not absolute")
)
type Path string
func (p Path) MarshalJSON() ([]byte, error) {
return []byte(`"` + string(p) + `"`), nil
}
type SystemdUnit struct {
Name string `yaml:"name"`
Enable bool `yaml:"enable"`
Mask bool `yaml:"mask"`
Contents string `yaml:"contents"`
DropIns []SystemdUnitDropIn `yaml:"dropins"`
}
type SystemdUnitDropIn struct {
Name string `yaml:"name"`
Contents string `yaml:"contents"`
func (p Path) Validate() report.Report {
if !path.IsAbs(string(p)) {
return report.ReportFromError(ErrPathRelative, report.EntryError)
}
return report.Report{}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
type Raid struct {
Name string `json:"name"`
Level string `json:"level"`
Devices []Path `json:"devices,omitempty"`
Spares int `json:"spares,omitempty"`
}
func (n Raid) Validate() report.Report {
switch n.Level {
case "linear", "raid0", "0", "stripe":
if n.Spares != 0 {
return report.ReportFromError(fmt.Errorf("spares unsupported for %q arrays", n.Level), report.EntryError)
}
case "raid1", "1", "mirror":
case "raid4", "4":
case "raid5", "5":
case "raid6", "6":
case "raid10", "10":
default:
return report.ReportFromError(fmt.Errorf("unrecognized raid level: %q", n.Level), report.EntryError)
}
return report.Report{}
}

View File

@@ -0,0 +1,22 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type Storage struct {
Disks []Disk `json:"disks,omitempty"`
Arrays []Raid `json:"raid,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Files []File `json:"files,omitempty"`
}

View File

@@ -0,0 +1,19 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type Systemd struct {
Units []SystemdUnit `json:"units,omitempty"`
}

View File

@@ -0,0 +1,111 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"bytes"
"errors"
"fmt"
"path"
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/validate/report"
)
type SystemdUnit struct {
Name SystemdUnitName `json:"name,omitempty"`
Enable bool `json:"enable,omitempty"`
Mask bool `json:"mask,omitempty"`
Contents string `json:"contents,omitempty"`
DropIns []SystemdUnitDropIn `json:"dropins,omitempty"`
}
func (u SystemdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type SystemdUnitDropIn struct {
Name SystemdUnitDropInName `json:"name,omitempty"`
Contents string `json:"contents,omitempty"`
}
func (u SystemdUnitDropIn) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type SystemdUnitName string
func (n SystemdUnitName) Validate() report.Report {
switch path.Ext(string(n)) {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid systemd unit extension"), report.EntryError)
}
}
type SystemdUnitDropInName string
func (n SystemdUnitDropInName) Validate() report.Report {
switch path.Ext(string(n)) {
case ".conf":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid systemd unit drop-in extension"), report.EntryError)
}
}
type NetworkdUnit struct {
Name NetworkdUnitName `json:"name,omitempty"`
Contents string `json:"contents,omitempty"`
}
func (u NetworkdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type NetworkdUnitName string
func (n NetworkdUnitName) Validate() report.Report {
switch path.Ext(string(n)) {
case ".link", ".netdev", ".network":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid networkd unit extension"), report.EntryError)
}
}
func validateUnitContent(content string) error {
c := bytes.NewBufferString(content)
_, err := unit.Deserialize(c)
if err != nil {
return fmt.Errorf("invalid unit content: %s", err)
}
return nil
}

View File

@@ -0,0 +1,72 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"encoding/json"
"errors"
"net/url"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
var (
ErrInvalidScheme = errors.New("invalid url scheme")
)
type Url url.URL
func (u *Url) UnmarshalJSON(data []byte) error {
var tu string
if err := json.Unmarshal(data, &tu); err != nil {
return err
}
pu, err := url.Parse(tu)
if err != nil {
return err
}
*u = Url(*pu)
return nil
}
func (u Url) MarshalJSON() ([]byte, error) {
return []byte(`"` + u.String() + `"`), nil
}
func (u Url) String() string {
tu := url.URL(u)
return (&tu).String()
}
func (u Url) Validate() report.Report {
// Empty url is valid, indicates an empty file
if u.String() == "" {
return report.Report{}
}
switch url.URL(u).Scheme {
case "http", "https", "oem":
return report.Report{}
case "data":
if _, err := dataurl.DecodeString(u.String()); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
default:
return report.ReportFromError(ErrInvalidScheme, report.EntryError)
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type User struct {
Name string `json:"name,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`
Create *UserCreate `json:"create,omitempty"`
}
type UserCreate struct {
Uid *uint `json:"uid,omitempty"`
GECOS string `json:"gecos,omitempty"`
Homedir string `json:"homeDir,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
PrimaryGroup string `json:"primaryGroup,omitempty"`
Groups []string `json:"groups,omitempty"`
NoUserGroup bool `json:"noUserGroup,omitempty"`
System bool `json:"system,omitempty"`
NoLogInit bool `json:"noLogInit,omitempty"`
Shell string `json:"shell,omitempty"`
}

View File

@@ -0,0 +1,19 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type Verification struct {
Hash *Hash `json:"hash,omitempty"`
}

View File

@@ -20,7 +20,6 @@ import (
"reflect"
"strings"
"github.com/coreos/ignition/config/types"
"github.com/coreos/ignition/config/validate/report"
)
@@ -75,10 +74,7 @@ func Validate(vObj reflect.Value, ast AstNode, source io.ReadSeeker) (r report.R
((vObj.Kind() != reflect.Ptr) ||
(!vObj.IsNil() && !vObj.Elem().Type().Implements(reflect.TypeOf((*validator)(nil)).Elem()))) {
sub_r := obj.Validate()
if vObj.Type() != reflect.TypeOf(types.Config{}) {
// Config checks are done on the config as a whole and shouldn't get line numbers
sub_r.AddPosition(line, col, highlight)
}
sub_r.AddPosition(line, col, highlight)
r.Merge(sub_r)
// Dont recurse on invalid inner nodes, it mostly leads to bogus messages
@@ -122,12 +118,18 @@ type field struct {
}
// getFields returns a field of all the fields in the struct, including the fields of
// embedded structs.
// embedded structs and structs inside interface{}'s
func getFields(vObj reflect.Value) []field {
if vObj.Kind() != reflect.Struct {
return nil
}
ret := []field{}
for i := 0; i < vObj.Type().NumField(); i++ {
if vObj.Type().Field(i).Anonymous {
ret = append(ret, getFields(vObj.Field(i))...)
// in the case of an embedded type that is an alias to interface, extract the
// real type contained by the interface
realObj := reflect.ValueOf(vObj.Field(i).Interface())
ret = append(ret, getFields(realObj)...)
} else {
ret = append(ret, field{Type: vObj.Type().Field(i), Value: vObj.Field(i)})
}