Skip to content

Commit

Permalink
util: Extend YAML processing to handle more varieties of configuration
Browse files Browse the repository at this point in the history
Allow clusterctl to handle a variety of groups of objects under
`--cluster` and `--machine`. Search for the correct objects instead
of assuming a single document.

This opens up being able to use a single configuration file, kustomize
and combining cluster, machine and other objects together.

Signed-off-by: Naadir Jeewa <[email protected]>
  • Loading branch information
randomvariable committed Jan 28, 2019
1 parent 6205b2f commit e69fc89
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 22 deletions.
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"
"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)

if err != nil {
return nil, err
}

return cluster, nil
return &cluster, nil
}

func ParseMachinesYaml(file string) ([]*clusterv1.Machine, error) {
bytes, err := ioutil.ReadFile(file)

machineList, err := ParseMachineListYaml(file)

if err != nil {
return nil, err
return nil, errors.WithStack(err)
}

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 {
} else if err == io.EOF {
return MachineP(outs), nil
}
outs = append(outs, out.Items...)
}
}

func isMissingKind(err error) bool {
return strings.Contains(err.Error(), "Object 'Kind' is missing in")
}

func FindGVKInFile(file string, kind string) ([][]byte, error) {
b, err := ioutil.ReadFile(file)
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 {
return outs, nil
}

if err != nil {
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)
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
metadata:
name: machine1`

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

0 comments on commit e69fc89

Please sign in to comment.