Skip to content

Commit

Permalink
allow creating nodegroups on cluster create (#360)
Browse files Browse the repository at this point in the history
* allow creating nodegroups on cluster create

* Added test for parseNodeGroups
  • Loading branch information
jdewinne authored Feb 13, 2024
1 parent e650f85 commit 1771241
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 0 deletions.
53 changes: 53 additions & 0 deletions cli/cmd/cluster_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/moby/moby/pkg/namesgenerator"
Expand Down Expand Up @@ -37,6 +39,8 @@ https://docs.replicated.com/vendor/testing-how-to#limitations`,
cmd.Flags().StringVar(&r.args.createClusterTTL, "ttl", "", "Cluster TTL (duration, max 48h)")
cmd.Flags().DurationVar(&r.args.createClusterWaitDuration, "wait", time.Second*0, "Wait duration for cluster to be ready (leave empty to not wait)")
cmd.Flags().StringVar(&r.args.createClusterInstanceType, "instance-type", "", "The type of instance to use (e.g. m6i.large)")
cmd.Flags().StringArrayVar(&r.args.createClusterNodeGroups, "nodegroup", []string{}, "Node group to create (name=?,instance-type=?,nodes=?,disk=? format, can be specified multiple times)")

cmd.Flags().StringArrayVar(&r.args.createClusterTags, "tag", []string{}, "Tag to apply to the cluster (key=value format, can be specified multiple times)")

cmd.Flags().BoolVar(&r.args.createClusterDryRun, "dry-run", false, "Dry run")
Expand All @@ -58,6 +62,11 @@ func (r *runners) createCluster(_ *cobra.Command, args []string) error {
return errors.Wrap(err, "parse tags")
}

nodeGroups, err := parseNodeGroups(r.args.createClusterNodeGroups)
if err != nil {
return errors.Wrap(err, "parse node groups")
}

opts := kotsclient.CreateClusterOpts{
Name: r.args.createClusterName,
KubernetesDistribution: r.args.createClusterKubernetesDistribution,
Expand All @@ -66,6 +75,7 @@ func (r *runners) createCluster(_ *cobra.Command, args []string) error {
DiskGiB: r.args.createClusterDiskGiB,
TTL: r.args.createClusterTTL,
InstanceType: r.args.createClusterInstanceType,
NodeGroups: nodeGroups,
Tags: tags,
DryRun: r.args.createClusterDryRun,
}
Expand Down Expand Up @@ -143,3 +153,46 @@ func waitForCluster(kotsRestClient *kotsclient.VendorV3Client, id string, durati
time.Sleep(time.Second * 5)
}
}

func parseNodeGroups(nodeGroups []string) ([]kotsclient.NodeGroup, error) {
parsedNodeGroups := []kotsclient.NodeGroup{}
for _, nodeGroup := range nodeGroups {
field := strings.Split(nodeGroup, ",")
ng := kotsclient.NodeGroup{}
for _, f := range field {
fieldParsed := strings.SplitN(f, "=", 2)
if len(fieldParsed) != 2 {
return nil, errors.Errorf("invalid node group format: %s", nodeGroup)
}
parsedFieldKey := fieldParsed[0]
parsedFieldValue := fieldParsed[1]
switch parsedFieldKey {
case "name":
ng.Name = parsedFieldValue
case "instance-type":
ng.InstanceType = parsedFieldValue
case "nodes":
nodes, err := strconv.Atoi(parsedFieldValue)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse nodes value: %s", parsedFieldValue)
}
ng.Nodes = nodes
case "disk":
diskSize, err := strconv.Atoi(parsedFieldValue)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse disk value: %s", parsedFieldValue)
}
ng.Disk = diskSize
default:
return nil, errors.Errorf("invalid node group field: %s", parsedFieldKey)
}
}

// check if instanceType, nodes and disk are set (required)
if ng.InstanceType == "" || ng.Nodes == 0 || ng.Disk == 0 {
return nil, errors.Errorf("invalid node group format: %s", nodeGroup)
}
parsedNodeGroups = append(parsedNodeGroups, ng)
}
return parsedNodeGroups, nil
}
104 changes: 104 additions & 0 deletions cli/cmd/cluster_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package cmd

import (
"testing"

"github.com/replicatedhq/replicated/pkg/kotsclient"
)

func Test_parseNodeGroups(t *testing.T) {
type args struct {
nodeGroups []string
}
tests := []struct {
name string
args args
want []kotsclient.NodeGroup
wantErr bool
}{
{
name: "valid node group",
args: args{
nodeGroups: []string{
"name=ng1,instance-type=t2.medium,nodes=3,disk=20",
},
},
want: []kotsclient.NodeGroup{
{
Name: "ng1",
InstanceType: "t2.medium",
Nodes: 3,
Disk: 20,
},
},
wantErr: false,
},
{
name: "invalid node group",
args: args{
nodeGroups: []string{
"name=ng1,instance-type=t2.medium,nodes=3",
},
},
want: nil,
wantErr: true,
},
{
name: "invalid node group field",
args: args{
nodeGroups: []string{
"name=ng1,instance-type=t2.medium,nodes=3,disk=20,invalid=invalid",
},
},
want: nil,
wantErr: true,
},
{
name: "invalid node group value (nodes)",
args: args{
nodeGroups: []string{
"name=ng1,instance-type=t2.medium,nodes=invalid,disk=20",
},
},
want: nil,
wantErr: true,
},
{
name: "invalid node group value (disk)",
args: args{
nodeGroups: []string{
"name=ng1,instance-type=t2.medium,nodes=3,disk=invalid",
},
},
want: nil,
wantErr: true,
},
{
name: "invalid node group format",
args: args{
nodeGroups: []string{
"invalid",
},
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
got, err := parseNodeGroups(tt.args.nodeGroups)
if (err != nil) != tt.wantErr {
t.Errorf("%q. parseNodeGroups() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue
}
if len(got) != len(tt.want) {
t.Errorf("%q. parseNodeGroups() got = %v, want %v", tt.name, got, tt.want)
continue
}
for i := range got {
if got[i] != tt.want[i] {
t.Errorf("%q. parseNodeGroups() got = %v, want %v", tt.name, got, tt.want)
}
}
}

}
1 change: 1 addition & 0 deletions cli/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ type runnerArgs struct {
createClusterTTL string
createClusterWaitDuration time.Duration
createClusterInstanceType string
createClusterNodeGroups []string
createClusterTags []string

upgradeClusterKubernetesVersion string
Expand Down
10 changes: 10 additions & 0 deletions pkg/kotsclient/cluster_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type CreateClusterRequest struct {
NodeCount int `json:"node_count"`
DiskGiB int64 `json:"disk_gib"`
TTL string `json:"ttl"`
NodeGroups []NodeGroup `json:"node_groups"`
InstanceType string `json:"instance_type"`
Tags []types.Tag `json:"tags"`
}
Expand All @@ -35,10 +36,18 @@ type CreateClusterOpts struct {
DiskGiB int64
TTL string
InstanceType string
NodeGroups []NodeGroup
Tags []types.Tag
DryRun bool
}

type NodeGroup struct {
Name string `json:"name"`
InstanceType string `json:"instance_type"`
Nodes int `json:"node_count"`
Disk int `json:"disk_gib"`
}

type CreateClusterErrorResponse struct {
Error CreateClusterErrorError `json:"Error"`
}
Expand Down Expand Up @@ -66,6 +75,7 @@ func (c *VendorV3Client) CreateCluster(opts CreateClusterOpts) (*types.Cluster,
DiskGiB: opts.DiskGiB,
TTL: opts.TTL,
InstanceType: opts.InstanceType,
NodeGroups: opts.NodeGroups,
Tags: opts.Tags,
}

Expand Down

0 comments on commit 1771241

Please sign in to comment.