Skip to content

Commit 1771241

Browse files
authored
allow creating nodegroups on cluster create (#360)
* allow creating nodegroups on cluster create * Added test for parseNodeGroups
1 parent e650f85 commit 1771241

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

cli/cmd/cluster_create.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package cmd
33
import (
44
"fmt"
55
"os"
6+
"strconv"
7+
"strings"
68
"time"
79

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

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

65+
nodeGroups, err := parseNodeGroups(r.args.createClusterNodeGroups)
66+
if err != nil {
67+
return errors.Wrap(err, "parse node groups")
68+
}
69+
6170
opts := kotsclient.CreateClusterOpts{
6271
Name: r.args.createClusterName,
6372
KubernetesDistribution: r.args.createClusterKubernetesDistribution,
@@ -66,6 +75,7 @@ func (r *runners) createCluster(_ *cobra.Command, args []string) error {
6675
DiskGiB: r.args.createClusterDiskGiB,
6776
TTL: r.args.createClusterTTL,
6877
InstanceType: r.args.createClusterInstanceType,
78+
NodeGroups: nodeGroups,
6979
Tags: tags,
7080
DryRun: r.args.createClusterDryRun,
7181
}
@@ -143,3 +153,46 @@ func waitForCluster(kotsRestClient *kotsclient.VendorV3Client, id string, durati
143153
time.Sleep(time.Second * 5)
144154
}
145155
}
156+
157+
func parseNodeGroups(nodeGroups []string) ([]kotsclient.NodeGroup, error) {
158+
parsedNodeGroups := []kotsclient.NodeGroup{}
159+
for _, nodeGroup := range nodeGroups {
160+
field := strings.Split(nodeGroup, ",")
161+
ng := kotsclient.NodeGroup{}
162+
for _, f := range field {
163+
fieldParsed := strings.SplitN(f, "=", 2)
164+
if len(fieldParsed) != 2 {
165+
return nil, errors.Errorf("invalid node group format: %s", nodeGroup)
166+
}
167+
parsedFieldKey := fieldParsed[0]
168+
parsedFieldValue := fieldParsed[1]
169+
switch parsedFieldKey {
170+
case "name":
171+
ng.Name = parsedFieldValue
172+
case "instance-type":
173+
ng.InstanceType = parsedFieldValue
174+
case "nodes":
175+
nodes, err := strconv.Atoi(parsedFieldValue)
176+
if err != nil {
177+
return nil, errors.Wrapf(err, "failed to parse nodes value: %s", parsedFieldValue)
178+
}
179+
ng.Nodes = nodes
180+
case "disk":
181+
diskSize, err := strconv.Atoi(parsedFieldValue)
182+
if err != nil {
183+
return nil, errors.Wrapf(err, "failed to parse disk value: %s", parsedFieldValue)
184+
}
185+
ng.Disk = diskSize
186+
default:
187+
return nil, errors.Errorf("invalid node group field: %s", parsedFieldKey)
188+
}
189+
}
190+
191+
// check if instanceType, nodes and disk are set (required)
192+
if ng.InstanceType == "" || ng.Nodes == 0 || ng.Disk == 0 {
193+
return nil, errors.Errorf("invalid node group format: %s", nodeGroup)
194+
}
195+
parsedNodeGroups = append(parsedNodeGroups, ng)
196+
}
197+
return parsedNodeGroups, nil
198+
}

cli/cmd/cluster_create_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
6+
"github.com/replicatedhq/replicated/pkg/kotsclient"
7+
)
8+
9+
func Test_parseNodeGroups(t *testing.T) {
10+
type args struct {
11+
nodeGroups []string
12+
}
13+
tests := []struct {
14+
name string
15+
args args
16+
want []kotsclient.NodeGroup
17+
wantErr bool
18+
}{
19+
{
20+
name: "valid node group",
21+
args: args{
22+
nodeGroups: []string{
23+
"name=ng1,instance-type=t2.medium,nodes=3,disk=20",
24+
},
25+
},
26+
want: []kotsclient.NodeGroup{
27+
{
28+
Name: "ng1",
29+
InstanceType: "t2.medium",
30+
Nodes: 3,
31+
Disk: 20,
32+
},
33+
},
34+
wantErr: false,
35+
},
36+
{
37+
name: "invalid node group",
38+
args: args{
39+
nodeGroups: []string{
40+
"name=ng1,instance-type=t2.medium,nodes=3",
41+
},
42+
},
43+
want: nil,
44+
wantErr: true,
45+
},
46+
{
47+
name: "invalid node group field",
48+
args: args{
49+
nodeGroups: []string{
50+
"name=ng1,instance-type=t2.medium,nodes=3,disk=20,invalid=invalid",
51+
},
52+
},
53+
want: nil,
54+
wantErr: true,
55+
},
56+
{
57+
name: "invalid node group value (nodes)",
58+
args: args{
59+
nodeGroups: []string{
60+
"name=ng1,instance-type=t2.medium,nodes=invalid,disk=20",
61+
},
62+
},
63+
want: nil,
64+
wantErr: true,
65+
},
66+
{
67+
name: "invalid node group value (disk)",
68+
args: args{
69+
nodeGroups: []string{
70+
"name=ng1,instance-type=t2.medium,nodes=3,disk=invalid",
71+
},
72+
},
73+
want: nil,
74+
wantErr: true,
75+
},
76+
{
77+
name: "invalid node group format",
78+
args: args{
79+
nodeGroups: []string{
80+
"invalid",
81+
},
82+
},
83+
want: nil,
84+
wantErr: true,
85+
},
86+
}
87+
for _, tt := range tests {
88+
got, err := parseNodeGroups(tt.args.nodeGroups)
89+
if (err != nil) != tt.wantErr {
90+
t.Errorf("%q. parseNodeGroups() error = %v, wantErr %v", tt.name, err, tt.wantErr)
91+
continue
92+
}
93+
if len(got) != len(tt.want) {
94+
t.Errorf("%q. parseNodeGroups() got = %v, want %v", tt.name, got, tt.want)
95+
continue
96+
}
97+
for i := range got {
98+
if got[i] != tt.want[i] {
99+
t.Errorf("%q. parseNodeGroups() got = %v, want %v", tt.name, got, tt.want)
100+
}
101+
}
102+
}
103+
104+
}

cli/cmd/runner.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ type runnerArgs struct {
177177
createClusterTTL string
178178
createClusterWaitDuration time.Duration
179179
createClusterInstanceType string
180+
createClusterNodeGroups []string
180181
createClusterTags []string
181182

182183
upgradeClusterKubernetesVersion string

pkg/kotsclient/cluster_create.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type CreateClusterRequest struct {
1717
NodeCount int `json:"node_count"`
1818
DiskGiB int64 `json:"disk_gib"`
1919
TTL string `json:"ttl"`
20+
NodeGroups []NodeGroup `json:"node_groups"`
2021
InstanceType string `json:"instance_type"`
2122
Tags []types.Tag `json:"tags"`
2223
}
@@ -35,10 +36,18 @@ type CreateClusterOpts struct {
3536
DiskGiB int64
3637
TTL string
3738
InstanceType string
39+
NodeGroups []NodeGroup
3840
Tags []types.Tag
3941
DryRun bool
4042
}
4143

44+
type NodeGroup struct {
45+
Name string `json:"name"`
46+
InstanceType string `json:"instance_type"`
47+
Nodes int `json:"node_count"`
48+
Disk int `json:"disk_gib"`
49+
}
50+
4251
type CreateClusterErrorResponse struct {
4352
Error CreateClusterErrorError `json:"Error"`
4453
}
@@ -66,6 +75,7 @@ func (c *VendorV3Client) CreateCluster(opts CreateClusterOpts) (*types.Cluster,
6675
DiskGiB: opts.DiskGiB,
6776
TTL: opts.TTL,
6877
InstanceType: opts.InstanceType,
78+
NodeGroups: opts.NodeGroups,
6979
Tags: opts.Tags,
7080
}
7181

0 commit comments

Comments
 (0)