Skip to content

Commit

Permalink
Merge pull request #2954 from chrislovecnm/create-yaml-or-json
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue.

Create YAML or JSON Clusterspec without creating the cluster

Allowing a user to create a YAML or JSON cluster or instance group without creating the object.  Some of the new methods will be used to fix the problems we are having with JSON output as well.  Reading an array of JSON objects is not yet supported by `kops create -f`, but a single JSON object is supported.

This implements

`kops create cluster --dry-run -oyaml`

TODOs

- [x] init or create
- [x] update cobra docs
- [x] figure out what is going on with unit tests
- [ ] figure out was issue(s) this closes
  • Loading branch information
Kubernetes Submit Queue authored Nov 5, 2017
2 parents 88984d4 + 4c82a6d commit 40a94a7
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 89 deletions.
1 change: 1 addition & 0 deletions cmd/kops/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func RunCreate(f *util.Factory, out io.Writer, c *CreateOptions) error {
return fmt.Errorf("error reading file %q: %v", f, err)
}

// TODO: this does not support a JSON array
sections := bytes.Split(contents, []byte("\n---\n"))
for _, section := range sections {
defaults := &schema.GroupVersionKind{
Expand Down
49 changes: 48 additions & 1 deletion cmd/kops/create_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/golang/glog"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kops"
"k8s.io/kops/cmd/kops/util"
Expand Down Expand Up @@ -124,6 +125,11 @@ type CreateClusterOptions struct {

// ConfigBase is the location where we will store the configuration, it defaults to the state store
ConfigBase string

// DryRun mode output a cluster manifest of Output type.
DryRun bool
// Output type during a DryRun
Output string
}

func (o *CreateClusterOptions) InitDefaults() {
Expand Down Expand Up @@ -190,6 +196,11 @@ var (
--project my-gce-project \
--image "ubuntu-os-cloud/ubuntu-1604-xenial-v20170202" \
--yes
# Create manifest for a cluster in AWS
kops create cluster --name=kubernetes-cluster.example.com \
--state=s3://kops-state-1234 --zones=eu-west-1a \
--node-count=2 --dry-run -oyaml
`))

create_cluster_short = i18n.T("Create a Kubernetes cluster.")
Expand Down Expand Up @@ -227,7 +238,7 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {
}

cmd.Flags().BoolVar(&options.Yes, "yes", options.Yes, "Specify --yes to immediately create the cluster")
cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform, cloudformation")
cmd.Flags().StringVar(&options.Target, "target", options.Target, fmt.Sprintf("Valid targets: %s, %s, %s. Set this flag to %s if you want kops to generate terraform", cloudup.TargetDirect, cloudup.TargetTerraform, cloudup.TargetDirect, cloudup.TargetTerraform))
cmd.Flags().StringVar(&options.Models, "model", options.Models, "Models to apply (separate multiple models with commas)")

// Configuration / state location
Expand Down Expand Up @@ -297,6 +308,10 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {

cmd.Flags().StringVar(&options.APILoadBalancerType, "api-loadbalancer-type", options.APILoadBalancerType, "Sets the API loadbalancer type to either 'public' or 'internal'")

// DryRun mode that will print YAML or JSON
cmd.Flags().BoolVar(&options.DryRun, "dry-run", options.DryRun, "If true, only print the object that would be sent, without sending it. This flag can be used to create a cluster YAML or JSON manifest.")
cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Ouput format. One of json|yaml. Used with the --dry-run flag.")

if featureflag.SpecOverrideFlag.Enabled() {
cmd.Flags().StringSliceVar(&options.Overrides, "override", options.Overrides, "Directly configure values in the spec")
}
Expand Down Expand Up @@ -326,6 +341,11 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e
isDryrun = true
targetName = cloudup.TargetDryRun
}

if c.DryRun && c.Output == "" {
return fmt.Errorf("unable to execute --dry-run without setting --output")
}

clusterName := c.ClusterName
if clusterName == "" {
return fmt.Errorf("--name is required")
Expand Down Expand Up @@ -987,6 +1007,32 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e
return err
}

if c.DryRun {
var obj []runtime.Object
obj = append(obj, cluster)

for _, group := range fullInstanceGroups {
// Cluster name is not populated, and we need it
group.ObjectMeta.Labels = make(map[string]string)
group.ObjectMeta.Labels[api.LabelClusterName] = cluster.ObjectMeta.Name
obj = append(obj, group)
}
switch c.Output {
case OutputYaml:
if err := fullOutputYAML(out, obj...); err != nil {
return fmt.Errorf("error writing cluster yaml to stdout: %v", err)
}
return nil
case OutputJSON:
if err := fullOutputJSON(out, obj...); err != nil {
return fmt.Errorf("error writing cluster json to stdout: %v", err)
}
return nil
default:
return fmt.Errorf("unsupported output type %q", c.Output)
}
}

// Note we perform as much validation as we can, before writing a bad config
err = registry.CreateClusterConfig(clientset, cluster, fullInstanceGroups)
if err != nil {
Expand All @@ -1010,6 +1056,7 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e
}
}

// Can we acutally get to this if??
if targetName != "" {
if isDryrun {
fmt.Fprintf(out, "Previewing changes that will be made:\n\n")
Expand Down
37 changes: 37 additions & 0 deletions cmd/kops/create_ig.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ import (
type CreateInstanceGroupOptions struct {
Role string
Subnets []string
// DryRun mode output an ig manifest of Output type.
DryRun bool
// Output type during a DryRun
Output string
}

var (
Expand All @@ -52,6 +56,10 @@ var (
# Create an instancegroup for the k8s-cluster.example.com cluster.
kops create ig --name=k8s-cluster.example.com node-example \
--role node --subnet my-subnet-name
# Create a YAML manifest for an instancegroup for the k8s-cluster.example.com cluster.
kops create ig --name=k8s-cluster.example.com node-example \
--role node --subnet my-subnet-name --dry-run -oyaml
`))

create_ig_short = i18n.T(`Create an instancegroup.`)
Expand Down Expand Up @@ -85,6 +93,9 @@ func NewCmdCreateInstanceGroup(f *util.Factory, out io.Writer) *cobra.Command {

cmd.Flags().StringVar(&options.Role, "role", options.Role, "Type of instance group to create ("+strings.Join(allRoles, ",")+")")
cmd.Flags().StringSliceVar(&options.Subnets, "subnet", options.Subnets, "Subnets in which to create instance group")
// DryRun mode that will print YAML or JSON
cmd.Flags().BoolVar(&options.DryRun, "dry-run", options.DryRun, "If true, only print the object that would be sent, without sending it. This flag can be used to create a cluster YAML or JSON manifest.")
cmd.Flags().StringVarP(&options.Output, "output", "o", options.Output, "Ouput format. One of json|yaml")

return cmd
}
Expand Down Expand Up @@ -142,6 +153,32 @@ func RunCreateInstanceGroup(f *util.Factory, cmd *cobra.Command, args []string,
return err
}

if options.DryRun {

if options.Output == "" {
return fmt.Errorf("must set output flag; yaml or json")
}

// Cluster name is not populated, and we need it
ig.ObjectMeta.Labels = make(map[string]string)
ig.ObjectMeta.Labels[api.LabelClusterName] = cluster.ObjectMeta.Name

switch options.Output {
case OutputYaml:
if err := fullOutputYAML(out, ig); err != nil {
return fmt.Errorf("error writing cluster yaml to stdout: %v", err)
}
return nil
case OutputJSON:
if err := fullOutputJSON(out, ig); err != nil {
return fmt.Errorf("error writing cluster json to stdout: %v", err)
}
return nil
default:
return fmt.Errorf("unsupported output type %q", options.Output)
}
}

var (
edit = editor.NewDefaultEditor(editorEnvs)
)
Expand Down
41 changes: 16 additions & 25 deletions cmd/kops/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package main

import (
"encoding/json"
"fmt"
"io"

Expand Down Expand Up @@ -153,35 +152,27 @@ func RunGet(context Factory, out io.Writer, options *GetOptions) error {
return err
}

switch options.output {
case OutputYaml:

err = clusterOutputYAML(clusters, out)
if err != nil {
return err
var obj []runtime.Object
if options.output != OutputTable {
obj = append(obj, cluster)
for _, group := range instancegroups {
obj = append(obj, group)
}
}

if err := writeYAMLSep(out); err != nil {
return err
switch options.output {
case OutputYaml:
if err := fullOutputYAML(out, obj...); err != nil {
return fmt.Errorf("error writing cluster yaml to stdout: %v", err)
}

err = igOutputYAML(instancegroups, out)
if err != nil {
return err
}
return nil

case OutputJSON:
return fmt.Errorf("not implemented")
// TODO this is not outputing valid json. Not sure what cluster and instance groups should look like
/*
err = clusterOutputJson(clusters,out)
if err != nil {
return err
}
err = igOutputJson(instancegroups,out)
if err != nil {
return err
}*/
if err := fullOutputJSON(out, obj...); err != nil {
return fmt.Errorf("error writing cluster json to stdout: %v", err)
}
return nil

case OutputTable:
fmt.Fprintf(os.Stdout, "Cluster\n")
Expand Down Expand Up @@ -235,7 +226,7 @@ func marshalYaml(obj runtime.Object) ([]byte, error) {

// obj must be a pointer to a marshalable object
func marshalJSON(obj runtime.Object) ([]byte, error) {
j, err := json.MarshalIndent(obj, "", " ")
j, err := kopscodecs.ToVersionedJSON(obj)
if err != nil {
return nil, fmt.Errorf("error marshaling json: %v", err)
}
Expand Down
54 changes: 42 additions & 12 deletions cmd/kops/get_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ package main

import (
"fmt"
"io"
"os"
"strings"

"io"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kops/cmd/kops/util"
api "k8s.io/kops/pkg/apis/kops"
Expand Down Expand Up @@ -133,7 +133,7 @@ func RunGetClusters(context Factory, out io.Writer, options *GetClusterOptions)
}

if len(clusters) == 0 {
return fmt.Errorf("No clusters found")
return fmt.Errorf("no clusters found")
}

if options.FullSpec {
Expand All @@ -146,14 +146,20 @@ func RunGetClusters(context Factory, out io.Writer, options *GetClusterOptions)
fmt.Fprint(out, get_cluster_full_warning)
}

var obj []runtime.Object
if options.output != OutputTable {
for _, c := range clusters {
obj = append(obj, c)
}
}

switch options.output {
case OutputTable:
return clusterOutputTable(clusters, out)
case OutputYaml:
return clusterOutputYAML(clusters, out)
return fullOutputYAML(out, obj...)
case OutputJSON:
return clusterOutputJson(clusters, out)

return fullOutputJSON(out, obj...)
default:
return fmt.Errorf("Unknown output format: %q", options.output)
}
Expand Down Expand Up @@ -206,23 +212,47 @@ func clusterOutputTable(clusters []*api.Cluster, out io.Writer) error {
return t.Render(clusters, out, "NAME", "CLOUD", "ZONES")
}

func clusterOutputJson(clusters []*api.Cluster, out io.Writer) error {
for _, cluster := range clusters {
if err := marshalToWriter(cluster, marshalJSON, out); err != nil {
// fullOutputJson outputs the marshalled JSON of a list of clusters and instance groups. It will handle
// nils for clusters and instanceGroups slices.
func fullOutputJSON(out io.Writer, args ...runtime.Object) error {
argsLen := len(args)

if argsLen > 1 {
if _, err := fmt.Fprint(out, "["); err != nil {
return err
}
}

for i, arg := range args {
if i != 0 {
if _, err := fmt.Fprint(out, ","); err != nil {
return err
}
}
if err := marshalToWriter(arg, marshalJSON, out); err != nil {
return err
}
}

if argsLen > 1 {
if _, err := fmt.Fprint(out, "]"); err != nil {
return err
}
}

return nil
}

func clusterOutputYAML(clusters []*api.Cluster, out io.Writer) error {
for i, cluster := range clusters {
// fullOutputJson outputs the marshalled JSON of a list of clusters and instance groups. It will handle
// nils for clusters and instanceGroups slices.
func fullOutputYAML(out io.Writer, args ...runtime.Object) error {
for i, obj := range args {
if i != 0 {
if err := writeYAMLSep(out); err != nil {
return fmt.Errorf("error writing to stdout: %v", err)
}
}
if err := marshalToWriter(cluster, marshalYaml, out); err != nil {
if err := marshalToWriter(obj, marshalYaml, out); err != nil {
return err
}
}
Expand Down
Loading

0 comments on commit 40a94a7

Please sign in to comment.