Repository: Azure/ARO-RP
Pull request #1530 (2021-06-04 02:20:12)

Don't allow Setting AlertManager PVC
### Which issue this PR addresses:

Wrote this while we were talking about it in the review meeting.  Empty out the PVC for Alertmanagermain as it's configurable from an openshift-monitoring perspective.  




Patch:
diff --git a/go.mod b/go.mod
index 80c4cc7d5..a961c45bf 100644
--- a/go.mod
+++ b/go.mod
@@ -243,7 +243,7 @@ replace (
 	github.com/openshift/cluster-api-provider-libvirt => github.com/openshift/cluster-api-provider-libvirt v0.2.1-0.20210324200850-033be25ca038
 	github.com/openshift/cluster-api-provider-ovirt => github.com/openshift/cluster-api-provider-ovirt v0.1.1-0.20210409185359-01b9bf8368a3
 	github.com/openshift/console-operator => github.com/openshift/console-operator v0.0.0-20210323072657-4f933d59784b
-	github.com/openshift/installer => github.com/mjudeikis/installer v0.9.0-master.0.20210521150000-41d78ae90525
+	github.com/openshift/installer => github.com/mjudeikis/installer v0.9.0-master.0.20210603071751-c3c375c5034a
 	github.com/openshift/library-go => github.com/openshift/library-go v0.0.0-20210205203934-9eb0d970f2f4
 	github.com/openshift/machine-api-operator => github.com/openshift/machine-api-operator v0.2.1-0.20210521181620-e179bb5ce397
 	github.com/openshift/machine-config-operator => github.com/openshift/machine-config-operator v0.0.1-0.20210522053223-c4b7e3f5118d
diff --git a/go.sum b/go.sum
index 832b3d628..200238e8b 100644
--- a/go.sum
+++ b/go.sum
@@ -1820,8 +1820,8 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
 github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/mjudeikis/go-cosmosdb v0.0.0-20210518104404-b205b3cefd36 h1:K0yb4fqa1rYPEbdlv3rYz0vh1aHK0Jkd+EscS1Yi5wI=
 github.com/mjudeikis/go-cosmosdb v0.0.0-20210518104404-b205b3cefd36/go.mod h1:l/LMyUQzbW3Pe62a6huel6S/5ED0KrkkqSQdwiGueUI=
-github.com/mjudeikis/installer v0.9.0-master.0.20210521150000-41d78ae90525 h1:5cm+cXaHTbZwhzvGuYf5XfcfpKT4gaBMYcB0eEc/FrU=
-github.com/mjudeikis/installer v0.9.0-master.0.20210521150000-41d78ae90525/go.mod h1:gK919VTSoMmqggOrjr5alO0KD7hkuE0I0xwoaitRpCQ=
+github.com/mjudeikis/installer v0.9.0-master.0.20210603071751-c3c375c5034a h1:CUatJN+U9y8AGqzfbvjQcnPKiJaK5gCZEIYpMqZr3/8=
+github.com/mjudeikis/installer v0.9.0-master.0.20210603071751-c3c375c5034a/go.mod h1:wRAY/doOkyi+sihbkl88EAMg11+3ZvPPBpGqI7iPVq0=
 github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ=
 github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
 github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
diff --git a/pkg/operator/controllers/monitoring/monitoring_controller.go b/pkg/operator/controllers/monitoring/monitoring_controller.go
index 12bd5b89a..c2d6bd84a 100644
--- a/pkg/operator/controllers/monitoring/monitoring_controller.go
+++ b/pkg/operator/controllers/monitoring/monitoring_controller.go
@@ -43,6 +43,12 @@ type Config struct {
 			api.MissingFields
 		} `json:"volumeClaimTemplate,omitempty"`
 	} `json:"prometheusK8s,omitempty"`
+	AlertManagerMain struct {
+		api.MissingFields
+		VolumeClaimTemplate struct {
+			api.MissingFields
+		} `json:"volumeClaimTemplate,omitempty"`
+	} `json:"alertmanagerMain,omitempty"`
 }
 
 var defaultConfig = `prometheusK8s: {}`
@@ -95,6 +101,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 		changed = true
 	}
 
+	if !reflect.DeepEqual(configData.AlertManagerMain.VolumeClaimTemplate, struct{ api.MissingFields }{}) {
+		configData.AlertManagerMain.VolumeClaimTemplate = struct{ api.MissingFields }{}
+		changed = true
+	}
+
 	if !isCreate && !changed {
 		return reconcile.Result{}, nil
 	}
diff --git a/vendor/github.com/coreos/go-systemd/LICENSE b/vendor/github.com/coreos/go-systemd/LICENSE
new file mode 100644
index 000000000..37ec93a14
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/vendor/github.com/coreos/go-systemd/NOTICE b/vendor/github.com/coreos/go-systemd/NOTICE
new file mode 100644
index 000000000..23a0ada2f
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/NOTICE
@@ -0,0 +1,5 @@
+CoreOS Project
+Copyright 2018 CoreOS, Inc
+
+This product includes software developed at CoreOS, Inc.
+(http://www.coreos.com/).
diff --git a/vendor/github.com/coreos/go-systemd/unit/deserialize.go b/vendor/github.com/coreos/go-systemd/unit/deserialize.go
new file mode 100644
index 000000000..c0c06bdfc
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/unit/deserialize.go
@@ -0,0 +1,278 @@
+// 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
+
+	// SYSTEMD_NEWLINE defines characters that systemd considers indicators
+	// for a newline.
+	SYSTEMD_NEWLINE = "\r\n"
+)
+
+var (
+	// ErrLineTooLong gets returned when a line is too long for systemd to handle.
+	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() {
+	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.ContainsAny(line, SYSTEMD_NEWLINE) {
+				l.errchan <- ErrLineTooLong
+				return
+			}
+		}
+
+		var err error
+		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 == ';'
+}
diff --git a/vendor/github.com/coreos/go-systemd/unit/escape.go b/vendor/github.com/coreos/go-systemd/unit/escape.go
new file mode 100644
index 000000000..63b11726d
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/unit/escape.go
@@ -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)
+}
diff --git a/vendor/github.com/coreos/go-systemd/unit/option.go b/vendor/github.com/coreos/go-systemd/unit/option.go
new file mode 100644
index 000000000..98e1af5c9
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/unit/option.go
@@ -0,0 +1,59 @@
+// 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"
+)
+
+// UnitOption represents an option in a systemd unit file.
+type UnitOption struct {
+	Section string
+	Name    string
+	Value   string
+}
+
+// NewUnitOption returns a new UnitOption instance with pre-set values.
+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)
+}
+
+// Match compares two UnitOptions and returns true if they are identical.
+func (uo *UnitOption) Match(other *UnitOption) bool {
+	return uo.Section == other.Section &&
+		uo.Name == other.Name &&
+		uo.Value == other.Value
+}
+
+// AllMatch compares two slices of UnitOptions and returns true if they are
+// identical.
+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
+}
diff --git a/vendor/github.com/coreos/go-systemd/unit/serialize.go b/vendor/github.com/coreos/go-systemd/unit/serialize.go
new file mode 100644
index 000000000..e07799cad
--- /dev/null
+++ b/vendor/github.com/coreos/go-systemd/unit/serialize.go
@@ -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)
+}
diff --git a/vendor/github.com/coreos/ignition/LICENSE b/vendor/github.com/coreos/ignition/LICENSE
new file mode 100644
index 000000000..e06d20818
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
+
diff --git a/vendor/github.com/coreos/ignition/NOTICE b/vendor/github.com/coreos/ignition/NOTICE
new file mode 100644
index 000000000..e520005cd
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/NOTICE
@@ -0,0 +1,5 @@
+CoreOS Project
+Copyright 2015 CoreOS, Inc
+
+This product includes software developed at CoreOS, Inc.
+(http://www.coreos.com/).
diff --git a/vendor/github.com/coreos/ignition/config/shared/errors/errors.go b/vendor/github.com/coreos/ignition/config/shared/errors/errors.go
new file mode 100644
index 000000000..f2b47cf90
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/shared/errors/errors.go
@@ -0,0 +1,109 @@
+// Copyright 2018 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 errors includes errors that are used in multiple config versions
+package errors
+
+import (
+	"errors"
+	"fmt"
+)
+
+var (
+	// Parsing / general errors
+	ErrInvalid            = errors.New("config is not valid")
+	ErrCloudConfig        = errors.New("not a config (found coreos-cloudconfig)")
+	ErrEmpty              = errors.New("not a config (empty)")
+	ErrUnknownVersion     = errors.New("unsupported config version")
+	ErrScript             = errors.New("not a config (found coreos-cloudinit script)")
+	ErrDeprecated         = errors.New("config format deprecated")
+	ErrCompressionInvalid = errors.New("invalid compression method")
+
+	// Ignition section errors
+	ErrOldVersion     = errors.New("incorrect config version (too old)")
+	ErrNewVersion     = errors.New("incorrect config version (too new)")
+	ErrInvalidVersion = errors.New("invalid config version (couldn't parse)")
+
+	// Storage section errors
+	ErrPermissionsUnset            = errors.New("permissions unset, defaulting to 0000")
+	ErrDiskDeviceRequired          = errors.New("disk device is required")
+	ErrPartitionNumbersCollide     = errors.New("partition numbers collide")
+	ErrPartitionsOverlap           = errors.New("partitions overlap")
+	ErrPartitionsMisaligned        = errors.New("partitions misaligned")
+	ErrAppendAndOverwrite          = errors.New("cannot set both append and overwrite to true")
+	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")
+	ErrUsedCreateAndMountOpts      = errors.New("cannot use both create object and mount-level options field")
+	ErrUsedCreateAndWipeFilesystem = errors.New("cannot use both create object and wipeFilesystem field")
+	ErrWarningCreateDeprecated     = errors.New("the create object has been deprecated in favor of mount-level options")
+	ErrExt4LabelTooLong            = errors.New("filesystem labels cannot be longer than 16 characters when using ext4")
+	ErrBtrfsLabelTooLong           = errors.New("filesystem labels cannot be longer than 256 characters when using btrfs")
+	ErrXfsLabelTooLong             = errors.New("filesystem labels cannot be longer than 12 characters when using xfs")
+	ErrSwapLabelTooLong            = errors.New("filesystem labels cannot be longer than 15 characters when using swap")
+	ErrVfatLabelTooLong            = errors.New("filesystem labels cannot be longer than 11 characters when using vfat")
+	ErrFileIllegalMode             = errors.New("illegal file mode")
+	ErrNoFilesystem                = errors.New("no filesystem specified")
+	ErrBothIDAndNameSet            = errors.New("cannot set both id and name")
+	ErrLabelTooLong                = errors.New("partition labels may not exceed 36 characters")
+	ErrDoesntMatchGUIDRegex        = errors.New("doesn't match the form \"01234567-89AB-CDEF-EDCB-A98765432101\"")
+	ErrLabelContainsColon          = errors.New("partition label will be truncated to text before the colon")
+	ErrPathRelative                = errors.New("path not absolute")
+	ErrSparesUnsupportedForLevel   = errors.New("spares unsupported for arrays with a level greater than 0")
+	ErrUnrecognizedRaidLevel       = errors.New("unrecognized raid level")
+	ErrShouldNotExistWithOthers    = errors.New("shouldExist specified false with other options also specified")
+	ErrZeroesWithShouldNotExist    = errors.New("shouldExist is false for a partition and other partition(s) has start or size 0")
+	ErrPartitionsUnitsMismatch     = errors.New("cannot mix MBs and sectors within a disk")
+	ErrSizeDeprecated              = errors.New("size is deprecated; use sizeMB instead")
+	ErrStartDeprecated             = errors.New("start is deprecated; use startMB instead")
+
+	// Passwd section errors
+	ErrPasswdCreateDeprecated      = errors.New("the create object has been deprecated in favor of user-level options")
+	ErrPasswdCreateAndGecos        = errors.New("cannot use both the create object and the user-level gecos field")
+	ErrPasswdCreateAndGroups       = errors.New("cannot use both the create object and the user-level groups field")
+	ErrPasswdCreateAndHomeDir      = errors.New("cannot use both the create object and the user-level homeDir field")
+	ErrPasswdCreateAndNoCreateHome = errors.New("cannot use both the create object and the user-level noCreateHome field")
+	ErrPasswdCreateAndNoLogInit    = errors.New("cannot use both the create object and the user-level noLogInit field")
+	ErrPasswdCreateAndNoUserGroup  = errors.New("cannot use both the create object and the user-level noUserGroup field")
+	ErrPasswdCreateAndPrimaryGroup = errors.New("cannot use both the create object and the user-level primaryGroup field")
+	ErrPasswdCreateAndShell        = errors.New("cannot use both the create object and the user-level shell field")
+	ErrPasswdCreateAndSystem       = errors.New("cannot use both the create object and the user-level system field")
+	ErrPasswdCreateAndUID          = errors.New("cannot use both the create object and the user-level uid field")
+
+	// Systemd and Networkd section errors
+	ErrInvalidSystemdExt        = errors.New("invalid systemd unit extension")
+	ErrInvalidSystemdDropinExt  = errors.New("invalid systemd drop-in extension")
+	ErrInvalidNetworkdExt       = errors.New("invalid networkd unit extension")
+	ErrInvalidNetworkdDropinExt = errors.New("invalid networkd drop-in extension")
+
+	// Misc errors
+	ErrInvalidScheme                   = errors.New("invalid url scheme")
+	ErrInvalidUrl                      = errors.New("unable to parse url")
+	ErrEmptyHTTPHeaderName             = errors.New("HTTP header name can't be empty")
+	ErrDuplicateHTTPHeaders            = errors.New("all header names in the list must be unique")
+	ErrUnsupportedSchemeForHTTPHeaders = errors.New("cannot use HTTP headers with this source scheme")
+	ErrHashMalformed                   = errors.New("malformed hash specifier")
+	ErrHashWrongSize                   = errors.New("incorrect size for hash sum")
+	ErrHashUnrecognized                = errors.New("unrecognized hash function")
+	ErrEngineConfiguration             = errors.New("engine incorrectly configured")
+
+	// AWS S3 specific errors
+	ErrInvalidS3ObjectVersionId = errors.New("invalid S3 object VersionId")
+)
+
+// NewNoInstallSectionError produces an error indicating the given unit, named
+// name, is missing an Install section.
+func NewNoInstallSectionError(name string) error {
+	return fmt.Errorf("unit %q is enabled, but has no install section so enable does nothing", name)
+}
diff --git a/vendor/github.com/coreos/ignition/config/shared/validations/unit.go b/vendor/github.com/coreos/ignition/config/shared/validations/unit.go
new file mode 100644
index 000000000..2924fac86
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/shared/validations/unit.go
@@ -0,0 +1,54 @@
+// Copyright 2018 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 validations contains validations shared between multiple config
+// versions.
+package validations
+
+import (
+	"github.com/coreos/go-systemd/unit"
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+// ValidateInstallSection is a helper to validate a given unit
+func ValidateInstallSection(name string, enabled bool, contentsEmpty bool, contentSections []*unit.UnitOption) report.Report {
+	if !enabled {
+		// install sections don't matter for not-enabled units
+		return report.Report{}
+	}
+	if contentsEmpty {
+		// install sections don't matter if it has no contents, e.g. it's being masked or just has dropins or such
+		return report.Report{}
+	}
+	if contentSections == nil {
+		// Should only happen if the unit could not be parsed, at which point an
+		// error is probably already in the report so we don't need to double-up on
+		// errors + warnings.
+		return report.Report{}
+	}
+
+	for _, section := range contentSections {
+		if section.Section == "Install" {
+			return report.Report{}
+		}
+	}
+
+	return report.Report{
+		Entries: []report.Entry{{
+			Message: errors.NewNoInstallSectionError(name).Error(),
+			Kind:    report.EntryWarning,
+		}},
+	}
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/ca.go b/vendor/github.com/coreos/ignition/config/v2_2/types/ca.go
new file mode 100644
index 000000000..7440e1e2f
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/ca.go
@@ -0,0 +1,27 @@
+// Copyright 2018 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 (
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (c CaReference) ValidateSource() report.Report {
+	err := validateURL(c.Source)
+	if err != nil {
+		return report.ReportFromError(err, report.EntryError)
+	}
+	return report.Report{}
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/config.go b/vendor/github.com/coreos/ignition/config/v2_2/types/config.go
new file mode 100644
index 000000000..b1fcfcd99
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/config.go
@@ -0,0 +1,91 @@
+// 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: 2,
+	}
+)
+
+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 checkNodeFilesystems(node Node, filesystems map[string]struct{}, nodeType string) report.Report {
+	r := report.Report{}
+	if node.Filesystem == "" {
+		// Filesystem was not specified. This is an error, but its handled in types.File's Validate, not here
+		return r
+	}
+	_, ok := filesystems[node.Filesystem]
+	if !ok {
+		r.Add(report.Entry{
+			Kind: report.EntryWarning,
+			Message: fmt.Sprintf("%v %q references nonexistent filesystem %q. (This is ok if it is defined in a referenced config)",
+				nodeType, node.Path, node.Filesystem),
+		})
+	}
+	return r
+}
+
+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 {
+		r.Merge(checkNodeFilesystems(file.Node, filesystems, "File"))
+	}
+	for _, link := range cfg.Storage.Links {
+		r.Merge(checkNodeFilesystems(link.Node, filesystems, "Link"))
+	}
+	for _, dir := range cfg.Storage.Directories {
+		r.Merge(checkNodeFilesystems(dir.Node, filesystems, "Directory"))
+	}
+}
+
+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{}{}
+	}
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/directory.go b/vendor/github.com/coreos/ignition/config/v2_2/types/directory.go
new file mode 100644
index 000000000..9fdc732a6
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/directory.go
@@ -0,0 +1,37 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (d Directory) ValidateMode() report.Report {
+	r := report.Report{}
+	if err := validateMode(d.Mode); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	if d.Mode == nil {
+		r.Add(report.Entry{
+			Message: errors.ErrPermissionsUnset.Error(),
+			Kind:    report.EntryWarning,
+		})
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/disk.go b/vendor/github.com/coreos/ignition/config/v2_2/types/disk.go
new file mode 100644
index 000000000..f0af504a1
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/disk.go
@@ -0,0 +1,128 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (n Disk) Validate() report.Report {
+	return report.Report{}
+}
+
+func (n Disk) ValidateDevice() report.Report {
+	if len(n.Device) == 0 {
+		return report.ReportFromError(errors.ErrDiskDeviceRequired, report.EntryError)
+	}
+	if err := validatePath(string(n.Device)); err != nil {
+		return report.ReportFromError(err, report.EntryError)
+	}
+	return report.Report{}
+}
+
+func (n Disk) ValidatePartitions() report.Report {
+	r := report.Report{}
+	if n.partitionNumbersCollide() {
+		r.Add(report.Entry{
+			Message: errors.ErrPartitionNumbersCollide.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	if n.partitionsOverlap() {
+		r.Add(report.Entry{
+			Message: errors.ErrPartitionsOverlap.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	if n.partitionsMisaligned() {
+		r.Add(report.Entry{
+			Message: errors.ErrPartitionsMisaligned.Error(),
+			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 {
+		if p.Number != 0 {
+			// a number of 0 means next available number, multiple devices can specify this
+			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() int {
+	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
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/file.go b/vendor/github.com/coreos/ignition/config/v2_2/types/file.go
new file mode 100644
index 000000000..b235d16c0
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/file.go
@@ -0,0 +1,69 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (f File) Validate() report.Report {
+	if f.Overwrite != nil && *f.Overwrite && f.Append {
+		return report.ReportFromError(errors.ErrAppendAndOverwrite, report.EntryError)
+	}
+	return report.Report{}
+}
+
+func (f File) ValidateMode() report.Report {
+	r := report.Report{}
+	if err := validateMode(f.Mode); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	if f.Mode == nil {
+		r.Add(report.Entry{
+			Message: errors.ErrPermissionsUnset.Error(),
+			Kind:    report.EntryWarning,
+		})
+	}
+	return r
+}
+
+func (fc FileContents) ValidateCompression() report.Report {
+	r := report.Report{}
+	switch fc.Compression {
+	case "", "gzip":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrCompressionInvalid.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (fc FileContents) ValidateSource() report.Report {
+	r := report.Report{}
+	err := validateURL(fc.Source)
+	if err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/filesystem.go b/vendor/github.com/coreos/ignition/config/v2_2/types/filesystem.go
new file mode 100644
index 000000000..a2e43ffda
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/filesystem.go
@@ -0,0 +1,144 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (f Filesystem) Validate() report.Report {
+	r := report.Report{}
+	if f.Mount == nil && f.Path == nil {
+		r.Add(report.Entry{
+			Message: errors.ErrFilesystemNoMountPath.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	if f.Mount != nil {
+		if f.Path != nil {
+			r.Add(report.Entry{
+				Message: errors.ErrFilesystemMountAndPath.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+		if f.Mount.Create != nil {
+			if f.Mount.WipeFilesystem {
+				r.Add(report.Entry{
+					Message: errors.ErrUsedCreateAndWipeFilesystem.Error(),
+					Kind:    report.EntryError,
+				})
+			}
+			if len(f.Mount.Options) > 0 {
+				r.Add(report.Entry{
+					Message: errors.ErrUsedCreateAndMountOpts.Error(),
+					Kind:    report.EntryError,
+				})
+			}
+			r.Add(report.Entry{
+				Message: errors.ErrWarningCreateDeprecated.Error(),
+				Kind:    report.EntryWarning,
+			})
+		}
+	}
+	return r
+}
+
+func (f Filesystem) ValidatePath() report.Report {
+	r := report.Report{}
+	if f.Path != nil && validatePath(*f.Path) != nil {
+		r.Add(report.Entry{
+			Message: errors.ErrPathRelative.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (m Mount) Validate() report.Report {
+	r := report.Report{}
+	switch m.Format {
+	case "ext4", "btrfs", "xfs", "swap", "vfat":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrFilesystemInvalidFormat.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (m Mount) ValidateDevice() report.Report {
+	r := report.Report{}
+	if err := validatePath(m.Device); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (m Mount) ValidateLabel() report.Report {
+	r := report.Report{}
+	if m.Label == nil {
+		return r
+	}
+	switch m.Format {
+	case "ext4":
+		if len(*m.Label) > 16 {
+			// source: man mkfs.ext4
+			r.Add(report.Entry{
+				Message: errors.ErrExt4LabelTooLong.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	case "btrfs":
+		if len(*m.Label) > 256 {
+			// source: man mkfs.btrfs
+			r.Add(report.Entry{
+				Message: errors.ErrBtrfsLabelTooLong.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	case "xfs":
+		if len(*m.Label) > 12 {
+			// source: man mkfs.xfs
+			r.Add(report.Entry{
+				Message: errors.ErrXfsLabelTooLong.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	case "swap":
+		// mkswap's man page does not state a limit on label size, but through
+		// experimentation it appears that mkswap will truncate long labels to
+		// 15 characters, so let's enforce that.
+		if len(*m.Label) > 15 {
+			r.Add(report.Entry{
+				Message: errors.ErrSwapLabelTooLong.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	case "vfat":
+		if len(*m.Label) > 11 {
+			// source: man mkfs.fat
+			r.Add(report.Entry{
+				Message: errors.ErrVfatLabelTooLong.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/ignition.go b/vendor/github.com/coreos/ignition/config/v2_2/types/ignition.go
new file mode 100644
index 000000000..bddf49583
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/ignition.go
@@ -0,0 +1,52 @@
+// 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 (
+	"github.com/coreos/go-semver/semver"
+
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (c ConfigReference) ValidateSource() report.Report {
+	r := report.Report{}
+	err := validateURL(c.Source)
+	if err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (v Ignition) Semver() (*semver.Version, error) {
+	return semver.NewVersion(v.Version)
+}
+
+func (v Ignition) Validate() report.Report {
+	tv, err := v.Semver()
+	if err != nil {
+		return report.ReportFromError(errors.ErrInvalidVersion, report.EntryError)
+	}
+	if MaxVersion.Major > tv.Major {
+		return report.ReportFromError(errors.ErrOldVersion, report.EntryError)
+	}
+	if MaxVersion.LessThan(*tv) {
+		return report.ReportFromError(errors.ErrNewVersion, report.EntryError)
+	}
+	return report.Report{}
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/mode.go b/vendor/github.com/coreos/ignition/config/v2_2/types/mode.go
new file mode 100644
index 000000000..d06045d67
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/mode.go
@@ -0,0 +1,26 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+)
+
+func validateMode(m *int) error {
+	if m != nil && (*m < 0 || *m > 07777) {
+		return errors.ErrFileIllegalMode
+	}
+	return nil
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/node.go b/vendor/github.com/coreos/ignition/config/v2_2/types/node.go
new file mode 100644
index 000000000..50badfdfb
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/node.go
@@ -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 (
+	"path/filepath"
+
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (n Node) ValidateFilesystem() report.Report {
+	r := report.Report{}
+	if n.Filesystem == "" {
+		r.Add(report.Entry{
+			Message: errors.ErrNoFilesystem.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (n Node) ValidatePath() report.Report {
+	r := report.Report{}
+	if err := validatePath(n.Path); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (n Node) Depth() int {
+	count := 0
+	for p := filepath.Clean(string(n.Path)); p != "/"; count++ {
+		p = filepath.Dir(p)
+	}
+	return count
+}
+
+func (nu NodeUser) Validate() report.Report {
+	r := report.Report{}
+	if nu.ID != nil && nu.Name != "" {
+		r.Add(report.Entry{
+			Message: errors.ErrBothIDAndNameSet.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+func (ng NodeGroup) Validate() report.Report {
+	r := report.Report{}
+	if ng.ID != nil && ng.Name != "" {
+		r.Add(report.Entry{
+			Message: errors.ErrBothIDAndNameSet.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/partition.go b/vendor/github.com/coreos/ignition/config/v2_2/types/partition.go
new file mode 100644
index 000000000..084dce7ce
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/partition.go
@@ -0,0 +1,77 @@
+// 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"
+	"strings"
+
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+const (
+	guidRegexStr = "^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$"
+)
+
+func (p Partition) ValidateLabel() report.Report {
+	r := 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(p.Label) > 36 {
+		r.Add(report.Entry{
+			Message: errors.ErrLabelTooLong.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	// sgdisk uses colons for delimitting compound arguments and does not allow escaping them.
+	if strings.Contains(p.Label, ":") {
+		r.Add(report.Entry{
+			Message: errors.ErrLabelContainsColon.Error(),
+			Kind:    report.EntryWarning,
+		})
+	}
+	return r
+}
+
+func (p Partition) ValidateTypeGUID() report.Report {
+	return validateGUID(p.TypeGUID)
+}
+
+func (p Partition) ValidateGUID() report.Report {
+	return validateGUID(p.GUID)
+}
+
+func validateGUID(guid string) report.Report {
+	r := report.Report{}
+	ok, err := regexp.MatchString(guidRegexStr, guid)
+	if err != nil {
+		r.Add(report.Entry{
+			Message: fmt.Sprintf("error matching guid regexp: %v", err),
+			Kind:    report.EntryError,
+		})
+	} else if !ok {
+		r.Add(report.Entry{
+			Message: errors.ErrDoesntMatchGUIDRegex.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/passwd.go b/vendor/github.com/coreos/ignition/config/v2_2/types/passwd.go
new file mode 100644
index 000000000..10508c56c
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/passwd.go
@@ -0,0 +1,67 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (p PasswdUser) Validate() report.Report {
+	r := report.Report{}
+	if p.Create != nil {
+		r.Add(report.Entry{
+			Message: errors.ErrPasswdCreateDeprecated.Error(),
+			Kind:    report.EntryWarning,
+		})
+		addErr := func(err error) {
+			r.Add(report.Entry{
+				Message: err.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+		if p.Gecos != "" {
+			addErr(errors.ErrPasswdCreateAndGecos)
+		}
+		if len(p.Groups) > 0 {
+			addErr(errors.ErrPasswdCreateAndGroups)
+		}
+		if p.HomeDir != "" {
+			addErr(errors.ErrPasswdCreateAndHomeDir)
+		}
+		if p.NoCreateHome {
+			addErr(errors.ErrPasswdCreateAndNoCreateHome)
+		}
+		if p.NoLogInit {
+			addErr(errors.ErrPasswdCreateAndNoLogInit)
+		}
+		if p.NoUserGroup {
+			addErr(errors.ErrPasswdCreateAndNoUserGroup)
+		}
+		if p.PrimaryGroup != "" {
+			addErr(errors.ErrPasswdCreateAndPrimaryGroup)
+		}
+		if p.Shell != "" {
+			addErr(errors.ErrPasswdCreateAndShell)
+		}
+		if p.System {
+			addErr(errors.ErrPasswdCreateAndSystem)
+		}
+		if p.UID != nil {
+			addErr(errors.ErrPasswdCreateAndUID)
+		}
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/path.go b/vendor/github.com/coreos/ignition/config/v2_2/types/path.go
new file mode 100644
index 000000000..780607c31
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/path.go
@@ -0,0 +1,28 @@
+// 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 (
+	"path"
+
+	"github.com/coreos/ignition/config/shared/errors"
+)
+
+func validatePath(p string) error {
+	if !path.IsAbs(p) {
+		return errors.ErrPathRelative
+	}
+	return nil
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/raid.go b/vendor/github.com/coreos/ignition/config/v2_2/types/raid.go
new file mode 100644
index 000000000..3aceaa9fa
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/raid.go
@@ -0,0 +1,57 @@
+// 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 (
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (n Raid) ValidateLevel() report.Report {
+	r := report.Report{}
+	switch n.Level {
+	case "linear", "raid0", "0", "stripe":
+		if n.Spares != 0 {
+			r.Add(report.Entry{
+				Message: errors.ErrSparesUnsupportedForLevel.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	case "raid1", "1", "mirror":
+	case "raid4", "4":
+	case "raid5", "5":
+	case "raid6", "6":
+	case "raid10", "10":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrUnrecognizedRaidLevel.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (n Raid) ValidateDevices() report.Report {
+	r := report.Report{}
+	for _, d := range n.Devices {
+		if err := validatePath(string(d)); err != nil {
+			r.Add(report.Entry{
+				Message: errors.ErrPathRelative.Error(),
+				Kind:    report.EntryError,
+			})
+		}
+	}
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/schema.go b/vendor/github.com/coreos/ignition/config/v2_2/types/schema.go
new file mode 100644
index 000000000..4b32b337b
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/schema.go
@@ -0,0 +1,246 @@
+package types
+
+// generated by "schematyper --package=types schema/ignition.json -o config/types/schema.go --root-type=Config" -- DO NOT EDIT
+
+type CaReference struct {
+	Source       string       `json:"source,omitempty"`
+	Verification Verification `json:"verification,omitempty"`
+}
+
+type Config struct {
+	Ignition Ignition `json:"ignition"`
+	Networkd Networkd `json:"networkd,omitempty"`
+	Passwd   Passwd   `json:"passwd,omitempty"`
+	Storage  Storage  `json:"storage,omitempty"`
+	Systemd  Systemd  `json:"systemd,omitempty"`
+}
+
+type ConfigReference struct {
+	Source       string       `json:"source,omitempty"`
+	Verification Verification `json:"verification,omitempty"`
+}
+
+type Create struct {
+	Force   bool           `json:"force,omitempty"`
+	Options []CreateOption `json:"options,omitempty"`
+}
+
+type CreateOption string
+
+type Device string
+
+type Directory struct {
+	Node
+	DirectoryEmbedded1
+}
+
+type DirectoryEmbedded1 struct {
+	Mode *int `json:"mode,omitempty"`
+}
+
+type Disk struct {
+	Device     string      `json:"device,omitempty"`
+	Partitions []Partition `json:"partitions,omitempty"`
+	WipeTable  bool        `json:"wipeTable,omitempty"`
+}
+
+type File struct {
+	Node
+	FileEmbedded1
+}
+
+type FileContents struct {
+	Compression  string       `json:"compression,omitempty"`
+	Source       string       `json:"source,omitempty"`
+	Verification Verification `json:"verification,omitempty"`
+}
+
+type FileEmbedded1 struct {
+	Append   bool         `json:"append,omitempty"`
+	Contents FileContents `json:"contents,omitempty"`
+	Mode     *int         `json:"mode,omitempty"`
+}
+
+type Filesystem struct {
+	Mount *Mount  `json:"mount,omitempty"`
+	Name  string  `json:"name,omitempty"`
+	Path  *string `json:"path,omitempty"`
+}
+
+type Group string
+
+type Ignition struct {
+	Config   IgnitionConfig `json:"config,omitempty"`
+	Security Security       `json:"security,omitempty"`
+	Timeouts Timeouts       `json:"timeouts,omitempty"`
+	Version  string         `json:"version,omitempty"`
+}
+
+type IgnitionConfig struct {
+	Append  []ConfigReference `json:"append,omitempty"`
+	Replace *ConfigReference  `json:"replace,omitempty"`
+}
+
+type Link struct {
+	Node
+	LinkEmbedded1
+}
+
+type LinkEmbedded1 struct {
+	Hard   bool   `json:"hard,omitempty"`
+	Target string `json:"target,omitempty"`
+}
+
+type Mount struct {
+	Create         *Create       `json:"create,omitempty"`
+	Device         string        `json:"device,omitempty"`
+	Format         string        `json:"format,omitempty"`
+	Label          *string       `json:"label,omitempty"`
+	Options        []MountOption `json:"options,omitempty"`
+	UUID           *string       `json:"uuid,omitempty"`
+	WipeFilesystem bool          `json:"wipeFilesystem,omitempty"`
+}
+
+type MountOption string
+
+type Networkd struct {
+	Units []Networkdunit `json:"units,omitempty"`
+}
+
+type NetworkdDropin struct {
+	Contents string `json:"contents,omitempty"`
+	Name     string `json:"name,omitempty"`
+}
+
+type Networkdunit struct {
+	Contents string           `json:"contents,omitempty"`
+	Dropins  []NetworkdDropin `json:"dropins,omitempty"`
+	Name     string           `json:"name,omitempty"`
+}
+
+type Node struct {
+	Filesystem string     `json:"filesystem,omitempty"`
+	Group      *NodeGroup `json:"group,omitempty"`
+	Overwrite  *bool      `json:"overwrite,omitempty"`
+	Path       string     `json:"path,omitempty"`
+	User       *NodeUser  `json:"user,omitempty"`
+}
+
+type NodeGroup struct {
+	ID   *int   `json:"id,omitempty"`
+	Name string `json:"name,omitempty"`
+}
+
+type NodeUser struct {
+	ID   *int   `json:"id,omitempty"`
+	Name string `json:"name,omitempty"`
+}
+
+type Partition struct {
+	GUID     string `json:"guid,omitempty"`
+	Label    string `json:"label,omitempty"`
+	Number   int    `json:"number,omitempty"`
+	Size     int    `json:"size,omitempty"`
+	Start    int    `json:"start,omitempty"`
+	TypeGUID string `json:"typeGuid,omitempty"`
+}
+
+type Passwd struct {
+	Groups []PasswdGroup `json:"groups,omitempty"`
+	Users  []PasswdUser  `json:"users,omitempty"`
+}
+
+type PasswdGroup struct {
+	Gid          *int   `json:"gid,omitempty"`
+	Name         string `json:"name,omitempty"`
+	PasswordHash string `json:"passwordHash,omitempty"`
+	System       bool   `json:"system,omitempty"`
+}
+
+type PasswdUser struct {
+	Create            *Usercreate        `json:"create,omitempty"`
+	Gecos             string             `json:"gecos,omitempty"`
+	Groups            []Group            `json:"groups,omitempty"`
+	HomeDir           string             `json:"homeDir,omitempty"`
+	Name              string             `json:"name,omitempty"`
+	NoCreateHome      bool               `json:"noCreateHome,omitempty"`
+	NoLogInit         bool               `json:"noLogInit,omitempty"`
+	NoUserGroup       bool               `json:"noUserGroup,omitempty"`
+	PasswordHash      *string            `json:"passwordHash,omitempty"`
+	PrimaryGroup      string             `json:"primaryGroup,omitempty"`
+	SSHAuthorizedKeys []SSHAuthorizedKey `json:"sshAuthorizedKeys,omitempty"`
+	Shell             string             `json:"shell,omitempty"`
+	System            bool               `json:"system,omitempty"`
+	UID               *int               `json:"uid,omitempty"`
+}
+
+type Raid struct {
+	Devices []Device     `json:"devices,omitempty"`
+	Level   string       `json:"level,omitempty"`
+	Name    string       `json:"name,omitempty"`
+	Options []RaidOption `json:"options,omitempty"`
+	Spares  int          `json:"spares,omitempty"`
+}
+
+type RaidOption string
+
+type SSHAuthorizedKey string
+
+type Security struct {
+	TLS TLS `json:"tls,omitempty"`
+}
+
+type Storage struct {
+	Directories []Directory  `json:"directories,omitempty"`
+	Disks       []Disk       `json:"disks,omitempty"`
+	Files       []File       `json:"files,omitempty"`
+	Filesystems []Filesystem `json:"filesystems,omitempty"`
+	Links       []Link       `json:"links,omitempty"`
+	Raid        []Raid       `json:"raid,omitempty"`
+}
+
+type Systemd struct {
+	Units []Unit `json:"units,omitempty"`
+}
+
+type SystemdDropin struct {
+	Contents string `json:"contents,omitempty"`
+	Name     string `json:"name,omitempty"`
+}
+
+type TLS struct {
+	CertificateAuthorities []CaReference `json:"certificateAuthorities,omitempty"`
+}
+
+type Timeouts struct {
+	HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
+	HTTPTotal           *int `json:"httpTotal,omitempty"`
+}
+
+type Unit struct {
+	Contents string          `json:"contents,omitempty"`
+	Dropins  []SystemdDropin `json:"dropins,omitempty"`
+	Enable   bool            `json:"enable,omitempty"`
+	Enabled  *bool           `json:"enabled,omitempty"`
+	Mask     bool            `json:"mask,omitempty"`
+	Name     string          `json:"name,omitempty"`
+}
+
+type Usercreate struct {
+	Gecos        string            `json:"gecos,omitempty"`
+	Groups       []UsercreateGroup `json:"groups,omitempty"`
+	HomeDir      string            `json:"homeDir,omitempty"`
+	NoCreateHome bool              `json:"noCreateHome,omitempty"`
+	NoLogInit    bool              `json:"noLogInit,omitempty"`
+	NoUserGroup  bool              `json:"noUserGroup,omitempty"`
+	PrimaryGroup string            `json:"primaryGroup,omitempty"`
+	Shell        string            `json:"shell,omitempty"`
+	System       bool              `json:"system,omitempty"`
+	UID          *int              `json:"uid,omitempty"`
+}
+
+type UsercreateGroup string
+
+type Verification struct {
+	Hash *string `json:"hash,omitempty"`
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/unit.go b/vendor/github.com/coreos/ignition/config/v2_2/types/unit.go
new file mode 100644
index 000000000..70fe1179a
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/unit.go
@@ -0,0 +1,131 @@
+// 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"
+	"path"
+	"strings"
+
+	"github.com/coreos/go-systemd/unit"
+
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/shared/validations"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+func (u Unit) ValidateContents() report.Report {
+	r := report.Report{}
+	opts, err := validateUnitContent(u.Contents)
+	if err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	isEnabled := u.Enable || (u.Enabled != nil && *u.Enabled)
+	r.Merge(validations.ValidateInstallSection(u.Name, isEnabled, u.Contents == "", opts))
+
+	return r
+}
+
+func (u Unit) ValidateName() report.Report {
+	r := report.Report{}
+	switch path.Ext(u.Name) {
+	case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrInvalidSystemdExt.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+	return r
+}
+
+func (d SystemdDropin) Validate() report.Report {
+	r := report.Report{}
+
+	if _, err := validateUnitContent(d.Contents); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	switch path.Ext(d.Name) {
+	case ".conf":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrInvalidSystemdDropinExt.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	return r
+}
+
+func (u Networkdunit) Validate() report.Report {
+	r := report.Report{}
+
+	if _, err := validateUnitContent(u.Contents); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	switch path.Ext(u.Name) {
+	case ".link", ".netdev", ".network":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrInvalidNetworkdExt.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	return r
+}
+
+func (d NetworkdDropin) Validate() report.Report {
+	r := report.Report{}
+
+	if _, err := validateUnitContent(d.Contents); err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	switch path.Ext(d.Name) {
+	case ".conf":
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrInvalidNetworkdDropinExt.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	return r
+}
+
+func validateUnitContent(content string) ([]*unit.UnitOption, error) {
+	c := strings.NewReader(content)
+	opts, err := unit.Deserialize(c)
+	if err != nil {
+		return nil, fmt.Errorf("invalid unit content: %s", err)
+	}
+	return opts, nil
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/url.go b/vendor/github.com/coreos/ignition/config/v2_2/types/url.go
new file mode 100644
index 000000000..11148fc08
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/url.go
@@ -0,0 +1,53 @@
+// 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"
+
+	"github.com/vincent-petithory/dataurl"
+
+	"github.com/coreos/ignition/config/shared/errors"
+)
+
+func validateURL(s string) error {
+	// Empty url is valid, indicates an empty file
+	if s == "" {
+		return nil
+	}
+	u, err := url.Parse(s)
+	if err != nil {
+		return errors.ErrInvalidUrl
+	}
+
+	switch u.Scheme {
+	case "http", "https", "oem", "tftp":
+		return nil
+	case "s3":
+		if v, ok := u.Query()["versionId"]; ok {
+			if len(v) == 0 || v[0] == "" {
+				return errors.ErrInvalidS3ObjectVersionId
+			}
+		}
+		return nil
+	case "data":
+		if _, err := dataurl.DecodeString(s); err != nil {
+			return err
+		}
+		return nil
+	default:
+		return errors.ErrInvalidScheme
+	}
+}
diff --git a/vendor/github.com/coreos/ignition/config/v2_2/types/verification.go b/vendor/github.com/coreos/ignition/config/v2_2/types/verification.go
new file mode 100644
index 000000000..51e7d1550
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/v2_2/types/verification.go
@@ -0,0 +1,77 @@
+// 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"
+	"strings"
+
+	"github.com/coreos/ignition/config/shared/errors"
+	"github.com/coreos/ignition/config/validate/report"
+)
+
+// HashParts will return the sum and function (in that order) of the hash stored
+// in this Verification, or an error if there is an issue during parsing.
+func (v Verification) HashParts() (string, string, error) {
+	if v.Hash == nil {
+		// The hash can be nil
+		return "", "", nil
+	}
+	parts := strings.SplitN(*v.Hash, "-", 2)
+	if len(parts) != 2 {
+		return "", "", errors.ErrHashMalformed
+	}
+
+	return parts[0], parts[1], nil
+}
+
+func (v Verification) Validate() report.Report {
+	r := report.Report{}
+
+	if v.Hash == nil {
+		// The hash can be nil
+		return r
+	}
+
+	function, sum, err := v.HashParts()
+	if err != nil {
+		r.Add(report.Entry{
+			Message: err.Error(),
+			Kind:    report.EntryError,
+		})
+		return r
+	}
+	var hash crypto.Hash
+	switch function {
+	case "sha512":
+		hash = crypto.SHA512
+	default:
+		r.Add(report.Entry{
+			Message: errors.ErrHashUnrecognized.Error(),
+			Kind:    report.EntryError,
+		})
+		return r
+	}
+
+	if len(sum) != hex.EncodedLen(hash.Size()) {
+		r.Add(report.Entry{
+			Message: errors.ErrHashWrongSize.Error(),
+			Kind:    report.EntryError,
+		})
+	}
+
+	return r
+}
diff --git a/vendor/github.com/coreos/ignition/config/validate/report/report.go b/vendor/github.com/coreos/ignition/config/validate/report/report.go
new file mode 100644
index 000000000..e0d4fed8d
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/config/validate/report/report.go
@@ -0,0 +1,158 @@
+// 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 report
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"sort"
+)
+
+type Report struct {
+	Entries []Entry
+}
+
+func (into *Report) Merge(from Report) {
+	into.Entries = append(into.Entries, from.Entries...)
+}
+
+func ReportFromError(err error, severity entryKind) Report {
+	if err == nil {
+		return Report{}
+	}
+	return Report{
+		Entries: []Entry{
+			{
+				Kind:    severity,
+				Message: err.Error(),
+			},
+		},
+	}
+}
+
+// Sort sorts the entries by line number, then column number
+func (r *Report) Sort() {
+	sort.Sort(entries(r.Entries))
+}
+
+type entries []Entry
+
+func (e entries) Len() int {
+	return len(e)
+}
+
+func (e entries) Swap(i, j int) {
+	e[i], e[j] = e[j], e[i]
+}
+
+func (e entries) Less(i, j int) bool {
+	if e[i].Line != e[j].Line {
+		return e[i].Line < e[j].Line
+	}
+	return e[i].Column < e[j].Column
+}
+
+const (
+	EntryError entryKind = iota
+	EntryWarning
+	EntryInfo
+	EntryDeprecated
+)
+
+// AddPosition updates all the entries with Line equal to 0 and sets the Line/Column fields to line/column. This is useful for
+// when a type has a custom unmarshaller and thus can't determine an exact offset of the error with the type. In this case
+// the offset for the entire chunk of json that got unmarshalled to the type can be used instead, which is still pretty good.
+func (r *Report) AddPosition(line, col int, highlight string) {
+	for i, e := range r.Entries {
+		if e.Line == 0 {
+			r.Entries[i].Line = line
+			r.Entries[i].Column = col
+			r.Entries[i].Highlight = highlight
+		}
+	}
+}
+
+func (r *Report) Add(e Entry) {
+	r.Entries = append(r.Entries, e)
+}
+
+func (r Report) String() string {
+	var errs bytes.Buffer
+	for i, entry := range r.Entries {
+		if i != 0 {
+			// Only add line breaks on multiline reports
+			errs.WriteString("\n")
+		}
+		errs.WriteString(entry.String())
+	}
+	return errs.String()
+}
+
+// IsFatal returns if there were any errors that make the config invalid
+func (r Report) IsFatal() bool {
+	for _, entry := range r.Entries {
+		if entry.Kind == EntryError {
+			return true
+		}
+	}
+	return false
+}
+
+// IsDeprecated returns if the report has deprecations
+func (r Report) IsDeprecated() bool {
+	for _, entry := range r.Entries {
+		if entry.Kind == EntryDeprecated {
+			return true
+		}
+	}
+	return false
+}
+
+type Entry struct {
+	Kind      entryKind `json:"kind"`
+	Message   string    `json:"message"`
+	Line      int       `json:"line,omitempty"`
+	Column    int       `json:"column,omitempty"`
+	Highlight string    `json:"-"`
+}
+
+func (e Entry) String() string {
+	if e.Line != 0 {
+		return fmt.Sprintf("%s at line %d, column %d\n%s%v", e.Kind.String(), e.Line, e.Column, e.Highlight, e.Message)
+	}
+	return fmt.Sprintf("%s: %v", e.Kind.String(), e.Message)
+}
+
+type entryKind int
+
+func (e entryKind) String() string {
+	switch e {
+	case EntryError:
+		return "error"
+	case EntryWarning:
+		return "warning"
+	case EntryInfo:
+		return "info"
+	case EntryDeprecated:
+		return "deprecated"
+	default:
+		return "unknown error"
+	}
+}
+
+func (e entryKind) MarshalJSON() ([]byte, error) {
+	return json.Marshal(e.String())
+}
diff --git a/vendor/github.com/openshift/installer/pkg/aro/dnsmasq/dnsmasq.go b/vendor/github.com/openshift/installer/pkg/aro/dnsmasq/dnsmasq.go
index ce744ddf8..dc759b4b0 100644
--- a/vendor/github.com/openshift/installer/pkg/aro/dnsmasq/dnsmasq.go
+++ b/vendor/github.com/openshift/installer/pkg/aro/dnsmasq/dnsmasq.go
@@ -6,9 +6,9 @@ import (
 	"fmt"
 	"text/template"
 
+	ign2types "github.com/coreos/ignition/config/v2_2/types"
 	ignutil "github.com/coreos/ignition/v2/config/util"
-	igntypes "github.com/coreos/ignition/v2/config/v3_2/types"
-	"github.com/openshift/installer/pkg/asset/ignition"
+	ign3types "github.com/coreos/ignition/v2/config/v3_2/types"
 	mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
 	"github.com/vincent-petithory/dataurl"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -81,7 +81,7 @@ func service() (string, error) {
 	return buf.String(), nil
 }
 
-func Ignition3Config(clusterDomain, apiIntIP, ingressIP string) (*igntypes.Config, error) {
+func Ignition2Config(clusterDomain, apiIntIP, ingressIP string) (*ign2types.Config, error) {
 	service, err := service()
 	if err != nil {
 		return nil, err
@@ -92,22 +92,69 @@ func Ignition3Config(clusterDomain, apiIntIP, ingressIP string) (*igntypes.Confi
 		return nil, err
 	}
 
-	return &igntypes.Config{
-		Ignition: igntypes.Ignition{
-			Version: igntypes.MaxVersion.String(),
+	return &ign2types.Config{
+		Ignition: ign2types.Ignition{
+			Version: ign2types.MaxVersion.String(),
 		},
-		Storage: igntypes.Storage{
-			Files: []igntypes.File{
+		Storage: ign2types.Storage{
+			Files: []ign2types.File{
 				{
-					Node: igntypes.Node{
+					Node: ign2types.Node{
+						Filesystem: "root",
+						Overwrite:  ignutil.BoolToPtr(true),
+						Path:       "/etc/dnsmasq.conf",
+						User: &ign2types.NodeUser{
+							Name: "root",
+						},
+					},
+					FileEmbedded1: ign2types.FileEmbedded1{
+						Contents: ign2types.FileContents{
+							Source: dataurl.EncodeBytes(config),
+						},
+						Mode: ignutil.IntToPtr(0644),
+					},
+				},
+			},
+		},
+		Systemd: ign2types.Systemd{
+			Units: []ign2types.Unit{
+				{
+					Contents: service,
+					Enabled:  ignutil.BoolToPtr(true),
+					Name:     "dnsmasq.service",
+				},
+			},
+		},
+	}, nil
+}
+
+func Ignition3Config(clusterDomain, apiIntIP, ingressIP string) (*ign3types.Config, error) {
+	service, err := service()
+	if err != nil {
+		return nil, err
+	}
+
+	config, err := config(clusterDomain, apiIntIP, ingressIP)
+	if err != nil {
+		return nil, err
+	}
+
+	return &ign3types.Config{
+		Ignition: ign3types.Ignition{
+			Version: ign3types.MaxVersion.String(),
+		},
+		Storage: ign3types.Storage{
+			Files: []ign3types.File{
+				{
+					Node: ign3types.Node{
 						Overwrite: ignutil.BoolToPtr(true),
 						Path:      "/etc/dnsmasq.conf",
-						User: igntypes.NodeUser{
+						User: ign3types.NodeUser{
 							Name: ignutil.StrToPtr("root"),
 						},
 					},
-					FileEmbedded1: igntypes.FileEmbedded1{
-						Contents: igntypes.Resource{
+					FileEmbedded1: ign3types.FileEmbedded1{
+						Contents: ign3types.Resource{
 							Source: ignutil.StrToPtr(dataurl.EncodeBytes(config)),
 						},
 						Mode: ignutil.IntToPtr(0644),
@@ -115,8 +162,8 @@ func Ignition3Config(clusterDomain, apiIntIP, ingressIP string) (*igntypes.Confi
 				},
 			},
 		},
-		Systemd: igntypes.Systemd{
-			Units: []igntypes.Unit{
+		Systemd: ign3types.Systemd{
+			Units: []ign3types.Unit{
 				{
 					Contents: &service,
 					Enabled:  ignutil.BoolToPtr(true),
@@ -128,12 +175,12 @@ func Ignition3Config(clusterDomain, apiIntIP, ingressIP string) (*igntypes.Confi
 }
 
 func MachineConfig(clusterDomain, apiIntIP, ingressIP, role string) (*mcfgv1.MachineConfig, error) {
-	ignConfig, err := Ignition3Config(clusterDomain, apiIntIP, ingressIP)
+	ignConfig, err := Ignition2Config(clusterDomain, apiIntIP, ingressIP)
 	if err != nil {
 		return nil, err
 	}
 
-	b, err := ignition.Marshal(ignConfig)
+	b, err := json.Marshal(ignConfig)
 	if err != nil {
 		return nil, err
 	}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 538b084aa..f0d2703fd 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -230,10 +230,17 @@ github.com/containers/storage/pkg/unshare
 github.com/coreos/go-oidc
 # github.com/coreos/go-semver v0.3.0
 github.com/coreos/go-semver/semver
+# github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
+github.com/coreos/go-systemd/unit
 # github.com/coreos/go-systemd/v22 v22.3.2
 ## explicit
 github.com/coreos/go-systemd/v22/journal
 github.com/coreos/go-systemd/v22/unit
+# github.com/coreos/ignition v0.35.0
+github.com/coreos/ignition/config/shared/errors
+github.com/coreos/ignition/config/shared/validations
+github.com/coreos/ignition/config/v2_2/types
+github.com/coreos/ignition/config/validate/report
 # github.com/coreos/ignition/v2 v2.10.1
 ## explicit
 github.com/coreos/ignition/v2/config/shared/errors
@@ -786,7 +793,7 @@ github.com/openshift/console-operator/pkg/api
 # github.com/openshift/custom-resource-status v1.1.0
 ## explicit
 github.com/openshift/custom-resource-status/conditions/v1
-# github.com/openshift/installer v0.16.1 => github.com/mjudeikis/installer v0.9.0-master.0.20210521150000-41d78ae90525
+# github.com/openshift/installer v0.16.1 => github.com/mjudeikis/installer v0.9.0-master.0.20210603071751-c3c375c5034a
 ## explicit
 github.com/openshift/installer/data
 github.com/openshift/installer/pkg/aro/dnsmasq
@@ -1910,7 +1917,7 @@ sigs.k8s.io/yaml
 # github.com/openshift/cluster-api-provider-libvirt => github.com/openshift/cluster-api-provider-libvirt v0.2.1-0.20210324200850-033be25ca038
 # github.com/openshift/cluster-api-provider-ovirt => github.com/openshift/cluster-api-provider-ovirt v0.1.1-0.20210409185359-01b9bf8368a3
 # github.com/openshift/console-operator => github.com/openshift/console-operator v0.0.0-20210323072657-4f933d59784b
-# github.com/openshift/installer => github.com/mjudeikis/installer v0.9.0-master.0.20210521150000-41d78ae90525
+# github.com/openshift/installer => github.com/mjudeikis/installer v0.9.0-master.0.20210603071751-c3c375c5034a
 # github.com/openshift/library-go => github.com/openshift/library-go v0.0.0-20210205203934-9eb0d970f2f4
 # github.com/openshift/machine-api-operator => github.com/openshift/machine-api-operator v0.2.1-0.20210521181620-e179bb5ce397
 # github.com/openshift/machine-config-operator => github.com/openshift/machine-config-operator v0.0.1-0.20210522053223-c4b7e3f5118d


Test patch:
diff --git a/pkg/operator/controllers/monitoring/monitoring_controller_test.go b/pkg/operator/controllers/monitoring/monitoring_controller_test.go
index d7ff70477..f237f0e4d 100644
--- a/pkg/operator/controllers/monitoring/monitoring_controller_test.go
+++ b/pkg/operator/controllers/monitoring/monitoring_controller_test.go
@@ -77,6 +77,7 @@ func TestReconcileMonitoringConfig(t *testing.T) {
 						Data: map[string]string{
 							"config.yaml": `
 prometheusK8s:
+  extraField: prometheus
   retention: 1d
   volumeClaimTemplate:
     metadata:
@@ -87,27 +88,17 @@ prometheusK8s:
           storage: 50Gi
       storageClassName: fast
       volumeMode: Filesystem
-`,
-						},
-					}),
-					log:        log,
-					jsonHandle: new(codec.JsonHandle),
-				}
-			},
-			wantConfig: `
-{}`,
-		},
-		{
-			name: "other monitoring components are configured",
-			setConfigMap: func() *Reconciler {
-				return &Reconciler{
-					kubernetescli: fake.NewSimpleClientset(&corev1.ConfigMap{
-						ObjectMeta: cmMetadata,
-						Data: map[string]string{
-							"config.yaml": `
 alertmanagerMain:
-  nodeSelector:
-    foo: bar
+  extraField: yeet
+  volumeClaimTemplate:
+    metadata:
+      name: slowest-storage
+    spec:
+      resources:
+        requests:
+          storage: 50Gi
+        storageClassName: snail-mail
+        volumeMode: Filesystem
 `,
 						},
 					}),
@@ -117,12 +108,13 @@ alertmanagerMain:
 			},
 			wantConfig: `
 alertmanagerMain:
-  nodeSelector:
-    foo: bar
+  extraField: yeet
+prometheusK8s:
+  extraField: prometheus
 `,
 		},
 		{
-			name: "enabled and we want to disable",
+			name: "other monitoring components are configured",
 			setConfigMap: func() *Reconciler {
 				return &Reconciler{
 					kubernetescli: fake.NewSimpleClientset(&corev1.ConfigMap{
@@ -132,13 +124,8 @@ alertmanagerMain:
 alertmanagerMain:
   nodeSelector:
     foo: bar
-prometheusK8s:
-    retention: 15d
-    volumeClaimTemplate:
-      spec:
-        resources:
-          requests:
-            storage: 100Gi
+somethingElse:
+  configured: true
 `,
 						},
 					}),
@@ -150,6 +137,8 @@ prometheusK8s:
 alertmanagerMain:
   nodeSelector:
     foo: bar
+somethingElse:
+  configured: true
 `,
 		},
 	} {