Skip to content

Commit

Permalink
Added ability for calicoctl to read multiple yaml documents in the sa…
Browse files Browse the repository at this point in the history
…me file
  • Loading branch information
mgleung committed Jul 7, 2017
1 parent 856d3ab commit 6b0d806
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 6 deletions.
35 changes: 29 additions & 6 deletions calicoctl/resourcemgr/resourcemgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import (
"reflect"
"strings"

"io/ioutil"
"io"
"os"

"github.com/projectcalico/libcalico-go/lib/api/unversioned"

"bytes"

log "github.com/Sirupsen/logrus"
yamlsep "github.com/projectcalico/calicoctl/calicoctl/util/yaml"
"github.com/projectcalico/go-yaml-wrapper"
"github.com/projectcalico/libcalico-go/lib/client"
"github.com/projectcalico/libcalico-go/lib/validator"
Expand Down Expand Up @@ -228,19 +229,41 @@ func unmarshalSliceOfResources(tml []unversioned.TypeMetadata, b []byte) ([]unve
// Resources. If the file does not contain any valid Resources this function returns an error.
func CreateResourcesFromFile(f string) ([]unversioned.Resource, error) {
// Load the bytes from file or from stdin.
var b []byte
var reader io.Reader
var err error

if f == "-" {
b, err = ioutil.ReadAll(os.Stdin)
reader = os.Stdin
} else {
b, err = ioutil.ReadFile(f)
reader, err = os.Open(f)
}
if err != nil {
return nil, err
}

return createResourcesFromBytes(b)
var resources []unversioned.Resource
separator := yamlsep.NewYAMLDocumentSeparator(reader)
for {
b, err := separator.Next()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}

r, err := createResourcesFromBytes(b)
if err != nil {
return nil, err
}

if resources == nil {
resources = r
} else {
resources = append(resources, r...)
}
}

return resources, nil
}

// Implement the ResourceManager interface on the resourceHelper struct.
Expand Down
81 changes: 81 additions & 0 deletions calicoctl/util/yaml/separator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2017 Tigera, Inc. All rights reserved.

// 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 yaml

import (
"bufio"
"bytes"
"io"
)

const yamlSeparator = "\n---"

type Separator interface {
Next() ([]byte, error)
}

type YAMLSeparator struct {
scanner *bufio.Scanner
}

func NewYAMLDocumentSeparator(reader io.Reader) Separator {
scanner := bufio.NewScanner(reader)
scanner.Split(splitYAMLDocument)
return &YAMLSeparator{
scanner: scanner,
}
}

// Function originally from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder.go
func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
sep := len([]byte(yamlSeparator))
if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 {
// We have a potential document terminator
i += sep
after := data[i:]
if len(after) == 0 {
// we can't read any more characters
if atEOF {
return len(data), data[:len(data)-sep], nil
}
return 0, nil, nil
}
if j := bytes.IndexByte(after, '\n'); j >= 0 {
return i + j + 1, data[0 : i-sep], nil
}
return 0, nil, nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}

func (t *YAMLSeparator) Next() ([]byte, error) {
if !t.scanner.Scan() {
err := t.scanner.Err()
if err == nil {
err = io.EOF
}
return []byte{}, err
}

return t.scanner.Bytes(), nil
}
117 changes: 117 additions & 0 deletions calicoctl/util/yaml/separator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) 2017 Tigera, Inc. All rights reserved.

// 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 yaml

import (
"bytes"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Test splitYAMLDocument", func() {
It("should not split up full documents without separators", func() {
ValidateSplitYAMLDocument("foo", true, "foo", 3)
})

It("should not return anything without a separator and not at the EOF", func() {
ValidateSplitYAMLDocument("fo", false, "", 0)
})

It("should return part of YAML separator at EOF", func() {
ValidateSplitYAMLDocument("---", true, "---", 3)
})

It("should return something similar to YAML separator with newline at EOF", func() {
ValidateSplitYAMLDocument("---\n", true, "---\n", 4)
})

It("should not return something similar to YAML separator if not at EOF", func() {
ValidateSplitYAMLDocument("---\n", false, "", 0)
})

It("should not return yaml separator before EOF but advance the bytes read", func() {
ValidateSplitYAMLDocument("\n---\n", false, "", 5)
})

It("should not return yaml separator at EOF but advance the bytes read", func() {
ValidateSplitYAMLDocument("\n---\n", true, "", 5)
})

It("should split out and read the first document in multiple documents", func() {
ValidateSplitYAMLDocument("abc\n---\ndef", true, "abc", 8)
})

It("should read the rest of the multiple documents at the EOF", func() {
ValidateSplitYAMLDocument("def", true, "def", 3)
})

It("should read nothing from an empty file", func() {
ValidateSplitYAMLDocument("", true, "", 0)
})
})

func ValidateSplitYAMLDocument(input string, atEOF bool, expect string, advAmt int) {
adv, token, err := splitYAMLDocument([]byte(input), atEOF)
Expect(err).NotTo(HaveOccurred())
Expect(expect).To(Equal(string(token)))
Expect(advAmt).To(Equal(adv))
}

var _ = Describe("Test YAML Separator Next", func() {
Context("with 2 YAML documents in one file", func() {
reader := bytes.NewReader([]byte(testYAMLDocFull))
separator := NewYAMLDocumentSeparator(reader)

var doc []byte
var err error
BeforeEach(func() {
doc, err = separator.Next()
})

It("should correctly separate the first document and return it", func() {
Expect(err).NotTo(HaveOccurred())
Expect(bytes.Equal([]byte(testYAMLDoc1), doc)).To(Equal(true))
})

It("should correctly separate the second document and return it", func() {
Expect(err).NotTo(HaveOccurred())
Expect(bytes.Equal([]byte(testYAMLDoc2), doc)).To(Equal(true))
})

It("should return an EOF error when there is nothing left", func() {
Expect(err).To(HaveOccurred())
Expect(len(doc)).To(Equal(0))
})
})
})

const testYAMLDoc1 = `
a: TestFieldA
b: TestFieldB
c:
c1: TestFieldC1
c2: TestFieldC2`

const testYAMLDoc2 = `
d: TestFieldD
e: TestFieldE
f:
- name: TestFieldFListItem1
- name: TestFieldFListItem2`

const testYAMLDocFull = testYAMLDoc1 + `
---
` + testYAMLDoc2
13 changes: 13 additions & 0 deletions calicoctl/util/yaml/yaml_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package yaml_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestYaml(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Yaml Suite")
}

0 comments on commit 6b0d806

Please sign in to comment.