mirror of
https://github.com/openshift/installer.git
synced 2026-02-05 15:47:14 +01:00
The installer now generates spec v3.1 ignition config, instead of v2.2 (and v2.4 for openstack) as before. The v3.1 ignition config specification can be found at [1]. A detailed overview of the differences between specs v2 and v3 can be found at [2]. Notable differences are: - the `Filesystem` identifier on ignition file configs no longer exists - `Overwrite` now defaults to `false` (was `true` in spec v2), which is why it is now set explicitly to keep the same behaviour. - duplicate file configs are now prohibited, i.e. all contents and all appendices must be defined in a single config. - duplicate systemd unit configs are now prohibited, i.e. the content and all dropins must be defined in a single config. This commit: - Bumps ignition to v2.3.0 with support for config spec v3.1. - Bumps terraform-provider-ignition to v2.1.0. Also adds downloading of the provider binary to `images/installer/Dockerfile.upi.ci` which is necessary because the ignition v2/spec3 version from the `community-terraform-providers/terraform-ignition-provider` fork is not present in the provider registry that is maintained by Hashicorp and can therefore not be pulled in automatically by terraform. is not present in the - Bumps machine-config-operator to b3b074ee9156 (latest commit at the time of this writing). - Adds "github.com/clarketm/json" dependency for marshaling Ignition configs. This is a dropin replacement for "encoding/json" that supports zero values of structs with omittempty annotations when marshaling. In effect, this will exclude empty pointer struct fields from the marshaled data instead of inserting nil values into them, which do not pass openAPI validation on fields that are supposed to contain e.g. strings. The same library is used by machine-config-operator and ignition itself. - Updates the vendor dir to make commit idempotent. [1] https://github.com/coreos/ignition/blob/master/doc/configuration-v3_1.md [2] https://github.com/coreos/ignition/blob/master/doc/migrating-configs.md#from-version-230-to-300 Co-authored-by: Vadim Rutkovsky <vrutkovs@redhat.com>
515 lines
13 KiB
Go
515 lines
13 KiB
Go
// Copyright 2010 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package json
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// A Decoder reads and decodes JSON values from an input stream.
|
|
type Decoder struct {
|
|
r io.Reader
|
|
buf []byte
|
|
d decodeState
|
|
scanp int // start of unread data in buf
|
|
scanned int64 // amount of data already scanned
|
|
scan scanner
|
|
err error
|
|
|
|
tokenState int
|
|
tokenStack []int
|
|
}
|
|
|
|
// NewDecoder returns a new decoder that reads from r.
|
|
//
|
|
// The decoder introduces its own buffering and may
|
|
// read data from r beyond the JSON values requested.
|
|
func NewDecoder(r io.Reader) *Decoder {
|
|
return &Decoder{r: r}
|
|
}
|
|
|
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
|
// Number instead of as a float64.
|
|
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
|
|
|
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
|
// is a struct and the input contains object keys which do not match any
|
|
// non-ignored, exported fields in the destination.
|
|
func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true }
|
|
|
|
// Decode reads the next JSON-encoded value from its
|
|
// input and stores it in the value pointed to by v.
|
|
//
|
|
// See the documentation for Unmarshal for details about
|
|
// the conversion of JSON into a Go value.
|
|
func (dec *Decoder) Decode(v interface{}) error {
|
|
if dec.err != nil {
|
|
return dec.err
|
|
}
|
|
|
|
if err := dec.tokenPrepareForDecode(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !dec.tokenValueAllowed() {
|
|
return &SyntaxError{msg: "not at beginning of value", Offset: dec.InputOffset()}
|
|
}
|
|
|
|
// Read whole value into buffer.
|
|
n, err := dec.readValue()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
|
dec.scanp += n
|
|
|
|
// Don't save err from unmarshal into dec.err:
|
|
// the connection is still usable since we read a complete JSON
|
|
// object from it before the error happened.
|
|
err = dec.d.unmarshal(v)
|
|
|
|
// fixup token streaming state
|
|
dec.tokenValueEnd()
|
|
|
|
return err
|
|
}
|
|
|
|
// Buffered returns a reader of the data remaining in the Decoder's
|
|
// buffer. The reader is valid until the next call to Decode.
|
|
func (dec *Decoder) Buffered() io.Reader {
|
|
return bytes.NewReader(dec.buf[dec.scanp:])
|
|
}
|
|
|
|
// readValue reads a JSON value into dec.buf.
|
|
// It returns the length of the encoding.
|
|
func (dec *Decoder) readValue() (int, error) {
|
|
dec.scan.reset()
|
|
|
|
scanp := dec.scanp
|
|
var err error
|
|
Input:
|
|
// help the compiler see that scanp is never negative, so it can remove
|
|
// some bounds checks below.
|
|
for scanp >= 0 {
|
|
|
|
// Look in the buffer for a new value.
|
|
for ; scanp < len(dec.buf); scanp++ {
|
|
c := dec.buf[scanp]
|
|
dec.scan.bytes++
|
|
switch dec.scan.step(&dec.scan, c) {
|
|
case scanEnd:
|
|
// scanEnd is delayed one byte so we decrement
|
|
// the scanner bytes count by 1 to ensure that
|
|
// this value is correct in the next call of Decode.
|
|
dec.scan.bytes--
|
|
break Input
|
|
case scanEndObject, scanEndArray:
|
|
// scanEnd is delayed one byte.
|
|
// We might block trying to get that byte from src,
|
|
// so instead invent a space byte.
|
|
if stateEndValue(&dec.scan, ' ') == scanEnd {
|
|
scanp++
|
|
break Input
|
|
}
|
|
case scanError:
|
|
dec.err = dec.scan.err
|
|
return 0, dec.scan.err
|
|
}
|
|
}
|
|
|
|
// Did the last read have an error?
|
|
// Delayed until now to allow buffer scan.
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
|
break Input
|
|
}
|
|
if nonSpace(dec.buf) {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
}
|
|
dec.err = err
|
|
return 0, err
|
|
}
|
|
|
|
n := scanp - dec.scanp
|
|
err = dec.refill()
|
|
scanp = dec.scanp + n
|
|
}
|
|
return scanp - dec.scanp, nil
|
|
}
|
|
|
|
func (dec *Decoder) refill() error {
|
|
// Make room to read more into the buffer.
|
|
// First slide down data already consumed.
|
|
if dec.scanp > 0 {
|
|
dec.scanned += int64(dec.scanp)
|
|
n := copy(dec.buf, dec.buf[dec.scanp:])
|
|
dec.buf = dec.buf[:n]
|
|
dec.scanp = 0
|
|
}
|
|
|
|
// Grow buffer if not large enough.
|
|
const minRead = 512
|
|
if cap(dec.buf)-len(dec.buf) < minRead {
|
|
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
|
copy(newBuf, dec.buf)
|
|
dec.buf = newBuf
|
|
}
|
|
|
|
// Read. Delay error for next iteration (after scan).
|
|
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
|
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
|
|
|
return err
|
|
}
|
|
|
|
func nonSpace(b []byte) bool {
|
|
for _, c := range b {
|
|
if !isSpace(c) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// An Encoder writes JSON values to an output stream.
|
|
type Encoder struct {
|
|
w io.Writer
|
|
err error
|
|
escapeHTML bool
|
|
|
|
indentBuf *bytes.Buffer
|
|
indentPrefix string
|
|
indentValue string
|
|
}
|
|
|
|
// NewEncoder returns a new encoder that writes to w.
|
|
func NewEncoder(w io.Writer) *Encoder {
|
|
return &Encoder{w: w, escapeHTML: true}
|
|
}
|
|
|
|
// Encode writes the JSON encoding of v to the stream,
|
|
// followed by a newline character.
|
|
//
|
|
// See the documentation for Marshal for details about the
|
|
// conversion of Go values to JSON.
|
|
func (enc *Encoder) Encode(v interface{}) error {
|
|
if enc.err != nil {
|
|
return enc.err
|
|
}
|
|
e := newEncodeState()
|
|
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Terminate each value with a newline.
|
|
// This makes the output look a little nicer
|
|
// when debugging, and some kind of space
|
|
// is required if the encoded value was a number,
|
|
// so that the reader knows there aren't more
|
|
// digits coming.
|
|
e.WriteByte('\n')
|
|
|
|
b := e.Bytes()
|
|
if enc.indentPrefix != "" || enc.indentValue != "" {
|
|
if enc.indentBuf == nil {
|
|
enc.indentBuf = new(bytes.Buffer)
|
|
}
|
|
enc.indentBuf.Reset()
|
|
err = Indent(enc.indentBuf, b, enc.indentPrefix, enc.indentValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b = enc.indentBuf.Bytes()
|
|
}
|
|
if _, err = enc.w.Write(b); err != nil {
|
|
enc.err = err
|
|
}
|
|
encodeStatePool.Put(e)
|
|
return err
|
|
}
|
|
|
|
// SetIndent instructs the encoder to format each subsequent encoded
|
|
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
|
|
// Calling SetIndent("", "") disables indentation.
|
|
func (enc *Encoder) SetIndent(prefix, indent string) {
|
|
enc.indentPrefix = prefix
|
|
enc.indentValue = indent
|
|
}
|
|
|
|
// SetEscapeHTML specifies whether problematic HTML characters
|
|
// should be escaped inside JSON quoted strings.
|
|
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
|
|
// to avoid certain safety problems that can arise when embedding JSON in HTML.
|
|
//
|
|
// In non-HTML settings where the escaping interferes with the readability
|
|
// of the output, SetEscapeHTML(false) disables this behavior.
|
|
func (enc *Encoder) SetEscapeHTML(on bool) {
|
|
enc.escapeHTML = on
|
|
}
|
|
|
|
// RawMessage is a raw encoded JSON value.
|
|
// It implements Marshaler and Unmarshaler and can
|
|
// be used to delay JSON decoding or precompute a JSON encoding.
|
|
type RawMessage []byte
|
|
|
|
// MarshalJSON returns m as the JSON encoding of m.
|
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
|
if m == nil {
|
|
return []byte("null"), nil
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// UnmarshalJSON sets *m to a copy of data.
|
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
|
if m == nil {
|
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
|
}
|
|
*m = append((*m)[0:0], data...)
|
|
return nil
|
|
}
|
|
|
|
var _ Marshaler = (*RawMessage)(nil)
|
|
var _ Unmarshaler = (*RawMessage)(nil)
|
|
|
|
// A Token holds a value of one of these types:
|
|
//
|
|
// Delim, for the four JSON delimiters [ ] { }
|
|
// bool, for JSON booleans
|
|
// float64, for JSON numbers
|
|
// Number, for JSON numbers
|
|
// string, for JSON string literals
|
|
// nil, for JSON null
|
|
//
|
|
type Token interface{}
|
|
|
|
const (
|
|
tokenTopValue = iota
|
|
tokenArrayStart
|
|
tokenArrayValue
|
|
tokenArrayComma
|
|
tokenObjectStart
|
|
tokenObjectKey
|
|
tokenObjectColon
|
|
tokenObjectValue
|
|
tokenObjectComma
|
|
)
|
|
|
|
// advance tokenstate from a separator state to a value state
|
|
func (dec *Decoder) tokenPrepareForDecode() error {
|
|
// Note: Not calling peek before switch, to avoid
|
|
// putting peek into the standard Decode path.
|
|
// peek is only called when using the Token API.
|
|
switch dec.tokenState {
|
|
case tokenArrayComma:
|
|
c, err := dec.peek()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c != ',' {
|
|
return &SyntaxError{"expected comma after array element", dec.InputOffset()}
|
|
}
|
|
dec.scanp++
|
|
dec.tokenState = tokenArrayValue
|
|
case tokenObjectColon:
|
|
c, err := dec.peek()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c != ':' {
|
|
return &SyntaxError{"expected colon after object key", dec.InputOffset()}
|
|
}
|
|
dec.scanp++
|
|
dec.tokenState = tokenObjectValue
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (dec *Decoder) tokenValueAllowed() bool {
|
|
switch dec.tokenState {
|
|
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (dec *Decoder) tokenValueEnd() {
|
|
switch dec.tokenState {
|
|
case tokenArrayStart, tokenArrayValue:
|
|
dec.tokenState = tokenArrayComma
|
|
case tokenObjectValue:
|
|
dec.tokenState = tokenObjectComma
|
|
}
|
|
}
|
|
|
|
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
|
type Delim rune
|
|
|
|
func (d Delim) String() string {
|
|
return string(d)
|
|
}
|
|
|
|
// Token returns the next JSON token in the input stream.
|
|
// At the end of the input stream, Token returns nil, io.EOF.
|
|
//
|
|
// Token guarantees that the delimiters [ ] { } it returns are
|
|
// properly nested and matched: if Token encounters an unexpected
|
|
// delimiter in the input, it will return an error.
|
|
//
|
|
// The input stream consists of basic JSON values—bool, string,
|
|
// number, and null—along with delimiters [ ] { } of type Delim
|
|
// to mark the start and end of arrays and objects.
|
|
// Commas and colons are elided.
|
|
func (dec *Decoder) Token() (Token, error) {
|
|
for {
|
|
c, err := dec.peek()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch c {
|
|
case '[':
|
|
if !dec.tokenValueAllowed() {
|
|
return dec.tokenError(c)
|
|
}
|
|
dec.scanp++
|
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
|
dec.tokenState = tokenArrayStart
|
|
return Delim('['), nil
|
|
|
|
case ']':
|
|
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
|
return dec.tokenError(c)
|
|
}
|
|
dec.scanp++
|
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
|
dec.tokenValueEnd()
|
|
return Delim(']'), nil
|
|
|
|
case '{':
|
|
if !dec.tokenValueAllowed() {
|
|
return dec.tokenError(c)
|
|
}
|
|
dec.scanp++
|
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
|
dec.tokenState = tokenObjectStart
|
|
return Delim('{'), nil
|
|
|
|
case '}':
|
|
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
|
return dec.tokenError(c)
|
|
}
|
|
dec.scanp++
|
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
|
dec.tokenValueEnd()
|
|
return Delim('}'), nil
|
|
|
|
case ':':
|
|
if dec.tokenState != tokenObjectColon {
|
|
return dec.tokenError(c)
|
|
}
|
|
dec.scanp++
|
|
dec.tokenState = tokenObjectValue
|
|
continue
|
|
|
|
case ',':
|
|
if dec.tokenState == tokenArrayComma {
|
|
dec.scanp++
|
|
dec.tokenState = tokenArrayValue
|
|
continue
|
|
}
|
|
if dec.tokenState == tokenObjectComma {
|
|
dec.scanp++
|
|
dec.tokenState = tokenObjectKey
|
|
continue
|
|
}
|
|
return dec.tokenError(c)
|
|
|
|
case '"':
|
|
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
|
var x string
|
|
old := dec.tokenState
|
|
dec.tokenState = tokenTopValue
|
|
err := dec.Decode(&x)
|
|
dec.tokenState = old
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dec.tokenState = tokenObjectColon
|
|
return x, nil
|
|
}
|
|
fallthrough
|
|
|
|
default:
|
|
if !dec.tokenValueAllowed() {
|
|
return dec.tokenError(c)
|
|
}
|
|
var x interface{}
|
|
if err := dec.Decode(&x); err != nil {
|
|
return nil, err
|
|
}
|
|
return x, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
|
var context string
|
|
switch dec.tokenState {
|
|
case tokenTopValue:
|
|
context = " looking for beginning of value"
|
|
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
|
context = " looking for beginning of value"
|
|
case tokenArrayComma:
|
|
context = " after array element"
|
|
case tokenObjectKey:
|
|
context = " looking for beginning of object key string"
|
|
case tokenObjectColon:
|
|
context = " after object key"
|
|
case tokenObjectComma:
|
|
context = " after object key:value pair"
|
|
}
|
|
return nil, &SyntaxError{"invalid character " + quoteChar(c) + context, dec.InputOffset()}
|
|
}
|
|
|
|
// More reports whether there is another element in the
|
|
// current array or object being parsed.
|
|
func (dec *Decoder) More() bool {
|
|
c, err := dec.peek()
|
|
return err == nil && c != ']' && c != '}'
|
|
}
|
|
|
|
func (dec *Decoder) peek() (byte, error) {
|
|
var err error
|
|
for {
|
|
for i := dec.scanp; i < len(dec.buf); i++ {
|
|
c := dec.buf[i]
|
|
if isSpace(c) {
|
|
continue
|
|
}
|
|
dec.scanp = i
|
|
return c, nil
|
|
}
|
|
// buffer has been scanned, now report any error
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = dec.refill()
|
|
}
|
|
}
|
|
|
|
// InputOffset returns the input stream byte offset of the current decoder position.
|
|
// The offset gives the location of the end of the most recently returned token
|
|
// and the beginning of the next token.
|
|
func (dec *Decoder) InputOffset() int64 {
|
|
return dec.scanned + int64(dec.scanp)
|
|
}
|