Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve YAML processing in clusterctl #716

Merged
merged 6 commits into from
Jan 28, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion pkg/util/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/cluster/v1alpha1:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

Expand Down
114 changes: 99 additions & 15 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ limitations under the License.
package util

import (
"bytes"
"context"
"fmt"
"github.com/pkg/errors"
"io"
"io/ioutil"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
"math/rand"
"os"
"os/exec"
Expand All @@ -28,11 +33,12 @@ import (
"time"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/klog"
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
)

const (
Expand Down Expand Up @@ -107,7 +113,7 @@ func GetMachineIfExists(c client.Client, namespace, name string) (*clusterv1.Mac
machine := &clusterv1.Machine{}
err := c.Get(context.Background(), client.ObjectKey{Namespace: namespace, Name: name}, machine)
if err != nil {
if errors.IsNotFound(err) {
if k8sErrors.IsNotFound(err) {
return nil, nil
}
return nil, err
Expand Down Expand Up @@ -178,33 +184,111 @@ func GetNamespaceOrDefault(namespace string) string {
}

func ParseClusterYaml(file string) (*clusterv1.Cluster, error) {
bytes, err := ioutil.ReadFile(file)
bytes, err := FindGVKInFile(file, "Cluster")
if err != nil {
return nil, err
}

cluster := &clusterv1.Cluster{}
if err := yaml.Unmarshal(bytes, cluster); err != nil {
var cluster clusterv1.Cluster

err = json.Unmarshal(bytes[0], &cluster)
randomvariable marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return nil, err
}

return cluster, nil
return &cluster, nil
}

func ParseMachinesYaml(file string) ([]*clusterv1.Machine, error) {
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
bytes, err := ioutil.ReadFile(file)

randomvariable marked this conversation as resolved.
Show resolved Hide resolved
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
machineList, err := ParseMachineListYaml(file)

if err != nil {
return nil, err
return nil, errors.WithStack(err)
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
}

list := &clusterv1.MachineList{}
if err := yaml.Unmarshal(bytes, &list); err != nil {
return nil, err
bytes, err := FindGVKInFile(file, "Machine")

// Original set of MachineLists did not have Kind field
if err != nil && !isMissingKind(err) {
return nil, errors.WithStack(err)
}

if list == nil {
return []*clusterv1.Machine{}, nil
machines := []clusterv1.Machine{}

for _, m := range bytes {
var machine clusterv1.Machine
err = json.Unmarshal(m, &machine)
if err != nil {
return nil, errors.WithStack(err)
}
machines = append(machines, machine)
}

return MachineP(list.Items), nil
machinesP := MachineP(machines)

return append(machinesP, machineList...), nil
}

func ParseMachineListYaml(file string) ([]*clusterv1.Machine, error) {
b, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.WithStack(err)
}

outs := []clusterv1.Machine{}

reader := bytes.NewReader(b)
decoder := yaml.NewYAMLOrJSONDecoder(reader, 32)

for {
var out clusterv1.MachineList
err = decoder.Decode(&out)

if err != io.EOF && err != nil {
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
} else if err == io.EOF {
return MachineP(outs), nil
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
}
outs = append(outs, out.Items...)
}
}

func isMissingKind(err error) bool {
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
return strings.Contains(err.Error(), "Object 'Kind' is missing in")
}

func FindGVKInFile(file string, kind string) ([][]byte, error) {
b, err := ioutil.ReadFile(file)
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return [][]byte{}, errors.WithStack(err)
}

outs := [][]byte{}

reader := bytes.NewReader(b)
decoder := yaml.NewYAMLOrJSONDecoder(reader, 32)

for {
var out unstructured.Unstructured
err = decoder.Decode(&out)

if runtime.IsMissingKind(err) || err == io.EOF {
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
return outs, nil
}

if err != nil {
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
return outs, errors.WithStack(err)
}

if out.GetKind() == kind && out.GetAPIVersion() == clusterv1.SchemeGroupVersion.String() {
var marshaled []byte
marshaled, err = out.MarshalJSON()
fmt.Printf("%T", err)
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return outs, errors.WithStack(err)
}
outs = append(outs, marshaled)
}
}
}
114 changes: 109 additions & 5 deletions pkg/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,86 @@ const validCluster = `
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Cluster
metadata:
name: cluster1
name: cluster1
spec:`

const validMachines = `
const validMachines1 = `
items:
- apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine1
spec:`

const validMachines2 = `
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine1
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine2`

const validUnified1 = `
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Cluster
metadata:
name: cluster1
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: MachineList
items:
- apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
metadata:
name: machine1`
randomvariable marked this conversation as resolved.
Show resolved Hide resolved

const validUnified2 = `
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Cluster
metadata:
name: cluster1
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine1
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine2`

const validUnified3 = `
apiVersion: v1
data:
cluster_name: cluster1
cluster_network_pods_cidrBlock: 192.168.0.0/16
cluster_network_services_cidrBlock: 10.96.0.0/12
cluster_sshKeyName: default
kind: ConfigMap
metadata:
name: cluster-api-shared-configuration
namespace: cluster-api-test
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Cluster
metadata:
name: cluster1
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine1
---
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
name: machine2`

func TestParseClusterYaml(t *testing.T) {
t.Run("File does not exist", func(t *testing.T) {
_, err := ParseClusterYaml("fileDoesNotExist")
Expand All @@ -55,6 +124,21 @@ func TestParseClusterYaml(t *testing.T) {
contents: validCluster,
expectedName: "cluster1",
},
{
name: "valid unified file with machine list",
contents: validUnified1,
expectedName: "cluster1",
},
{
name: "valid unified file with separate machines",
contents: validUnified2,
expectedName: "cluster1",
},
{
name: "valid unified file with separate machines and a configmap",
contents: validUnified3,
expectedName: "cluster1",
},
{
name: "gibberish in file",
contents: `blah ` + validCluster + ` blah`,
Expand Down Expand Up @@ -100,13 +184,33 @@ func TestParseMachineYaml(t *testing.T) {
expectedMachineCount int
}{
{
name: "valid file",
contents: validMachines,
name: "valid file using MachineList",
contents: validMachines1,
expectedMachineCount: 1,
},
{
name: "valid file using Machines",
contents: validMachines2,
expectedMachineCount: 2,
},
{
name: "valid unified file with machine list",
contents: validUnified1,
expectedMachineCount: 1,
},
{
name: "valid unified file with separate machines",
contents: validUnified2,
expectedMachineCount: 2,
},
{
name: "valid unified file with separate machines and a configmap",
contents: validUnified3,
expectedMachineCount: 2,
},
{
name: "gibberish in file",
contents: `blah ` + validMachines + ` blah`,
contents: `blah ` + validMachines1 + ` blah`,
expectErr: true,
},
}
Expand Down