diff --git a/cli/cmd/cluster_create.go b/cli/cmd/cluster_create.go index 66354967e..a32269afe 100644 --- a/cli/cmd/cluster_create.go +++ b/cli/cmd/cluster_create.go @@ -85,6 +85,7 @@ replicated cluster create --distribution eks --version 1.21 --nodes 3 --addon ob cmd.Flags().StringArrayVar(&r.args.createClusterAddons, "addon", []string{}, "Addons to install on the cluster (can be specified multiple times)") cmd.Flags().StringVar(&r.args.clusterAddonCreateObjectStoreBucket, "bucket-prefix", "", "A prefix for the bucket name to be created (required by '--addon object-store')") + cmd.Flags().StringVar(&r.args.createClusterNetworkPolicy, "network-policy", "", "The network policy to use for the cluster") cmd.Flags().BoolVar(&r.args.createClusterDryRun, "dry-run", false, "Dry run") @@ -143,6 +144,7 @@ func (r *runners) createCluster(cmd *cobra.Command, args []string) error { InstanceType: r.args.createClusterInstanceType, NodeGroups: nodeGroups, Tags: tags, + NetworkPolicy: r.args.createClusterNetworkPolicy, DryRun: r.args.createClusterDryRun, } if r.args.createClusterMinNodeCount != "" { diff --git a/cli/cmd/runner.go b/cli/cmd/runner.go index 52221d82a..d9f376d06 100644 --- a/cli/cmd/runner.go +++ b/cli/cmd/runner.go @@ -130,6 +130,7 @@ type runnerArgs struct { createClusterNodeGroups []string createClusterTags []string createClusterAddons []string + createClusterNetworkPolicy string upgradeClusterKubernetesVersion string upgradeClusterDryRun bool diff --git a/pact/kotsclient/cmx_test.go b/pact/kotsclient/cmx_test.go index ba3ae17c1..1507c794a 100644 --- a/pact/kotsclient/cmx_test.go +++ b/pact/kotsclient/cmx_test.go @@ -206,6 +206,78 @@ func Test_CreateCluster(t *testing.T) { } } +func Test_CreateClusterWithNetworkPolicy(t *testing.T) { + test := func() error { + u := fmt.Sprintf("http://localhost:%d", pact.Server.Port) + api := platformclient.NewHTTPClient(u, "cli-create-cluster-token") + client := realkotsclient.VendorV3Client{HTTPClient: *api} + + cluster, ve, err := client.CreateCluster(realkotsclient.CreateClusterOpts{ + Name: "vxlan-cluster-airgap", + KubernetesDistribution: "fake", + KubernetesVersion: "1.25", + NodeCount: 1, + DiskGiB: 50, + TTL: "2h", + InstanceType: "r1.small", + NetworkPolicy: "airgap", + Tags: []types.Tag{{Key: "test", Value: "pact"}}, + }) + require.NoError(t, err) + require.Nil(t, ve) + require.Equal(t, "vxlan-cluster-airgap", cluster.Name) + + return nil + } + + pact.AddInteraction(). + Given("Create CMX cluster"). + UponReceiving("A request to create a CMX cluster with a network policy"). + WithRequest(dsl.Request{ + Method: "POST", + Path: dsl.String("/v3/cluster"), + Headers: dsl.MapMatcher{ + "Authorization": dsl.String("cli-create-cluster-token"), + "Content-Type": dsl.String("application/json"), + }, + Body: map[string]interface{}{ + "name": "vxlan-cluster-airgap", + "kubernetes_distribution": "fake", + "kubernetes_version": "1.25", + "ip_family": "", + "license_id": "", + "node_count": 1, + "min_node_count": nil, + "max_node_count": nil, + "disk_gib": 50, + "ttl": "2h", + "node_groups": nil, + "instance_type": "r1.small", + "tags": []map[string]interface{}{{"key": "test", "value": "pact"}}, + "network_policy": "airgap", + }, + }). + WillRespondWith(dsl.Response{ + Status: 201, + Body: map[string]interface{}{ + "cluster": map[string]interface{}{ + "id": dsl.Like("0fed1234"), + "name": dsl.Like("vxlan-cluster-airgap"), + "kubernetes_distribution": dsl.Like("fake"), + "kubernetes_version": dsl.Like("1.25"), + "network_id": dsl.Like("befee8ca"), + "status": dsl.Like("queued"), + "ttl": dsl.Like("2h"), + "tags": []map[string]interface{}{{"key": "test", "value": "pact"}}, + }, + }, + }) + + if err := pact.Verify(test); err != nil { + t.Fatalf("Error on Verify: %v", err) + } +} + // Test_GetCluster is disabled because cluster fixtures defined in // migrations/fixtures/fixtures/*.yaml don't currently load into the test // database during init (a pre-existing bug — `vm`, `network`, etc. fixtures diff --git a/pacts/replicated-cli-vendor-api.json b/pacts/replicated-cli-vendor-api.json index e4c3df653..be110efd7 100644 --- a/pacts/replicated-cli-vendor-api.json +++ b/pacts/replicated-cli-vendor-api.json @@ -1417,6 +1417,84 @@ } } } + }, + { + "description": "A request to create a CMX cluster with a network policy", + "providerState": "Create CMX cluster", + "request": { + "method": "POST", + "path": "/v3/cluster", + "headers": { + "Authorization": "cli-create-cluster-token", + "Content-Type": "application/json" + }, + "body": { + "disk_gib": 50, + "instance_type": "r1.small", + "ip_family": "", + "kubernetes_distribution": "fake", + "kubernetes_version": "1.25", + "license_id": "", + "max_node_count": null, + "min_node_count": null, + "name": "vxlan-cluster-airgap", + "network_policy": "airgap", + "node_count": 1, + "node_groups": null, + "tags": [ + { + "key": "test", + "value": "pact" + } + ], + "ttl": "2h" + } + }, + "response": { + "status": 201, + "headers": { + }, + "body": { + "cluster": { + "id": "0fed1234", + "kubernetes_distribution": "fake", + "kubernetes_version": "1.25", + "name": "vxlan-cluster-airgap", + "network_id": "befee8ca", + "status": "queued", + "tags": [ + { + "key": "test", + "value": "pact" + } + ], + "ttl": "2h" + } + }, + "matchingRules": { + "$.body.cluster.id": { + "match": "type" + }, + "$.body.cluster.kubernetes_distribution": { + "match": "type" + }, + "$.body.cluster.kubernetes_version": { + "match": "type" + }, + "$.body.cluster.name": { + "match": "type" + }, + "$.body.cluster.network_id": { + "match": "type" + }, + "$.body.cluster.status": { + "match": "type" + }, + "$.body.cluster.ttl": { + "match": "type" + } + } + } } ], "metadata": { diff --git a/pkg/kotsclient/cluster_create.go b/pkg/kotsclient/cluster_create.go index 313009dc7..ab5302491 100644 --- a/pkg/kotsclient/cluster_create.go +++ b/pkg/kotsclient/cluster_create.go @@ -25,6 +25,7 @@ type CreateClusterRequest struct { NodeGroups []NodeGroup `json:"node_groups"` InstanceType string `json:"instance_type"` Tags []types.Tag `json:"tags"` + NetworkPolicy string `json:"network_policy,omitempty"` } type CreateClusterResponse struct { @@ -53,6 +54,7 @@ type CreateClusterOpts struct { InstanceType string NodeGroups []NodeGroup Tags []types.Tag + NetworkPolicy string DryRun bool } @@ -98,6 +100,7 @@ func (c *VendorV3Client) CreateCluster(opts CreateClusterOpts) (*types.Cluster, InstanceType: opts.InstanceType, NodeGroups: opts.NodeGroups, Tags: opts.Tags, + NetworkPolicy: opts.NetworkPolicy, } if opts.DryRun { diff --git a/pkg/kotsclient/create_request_test.go b/pkg/kotsclient/create_request_test.go index dec54d9ec..b869f312b 100644 --- a/pkg/kotsclient/create_request_test.go +++ b/pkg/kotsclient/create_request_test.go @@ -68,3 +68,32 @@ func TestCreateVMIncludesNetworkPolicy(t *testing.T) { require.Equal(t, "network-id", requestBody["network_id"]) require.Equal(t, "airgap", requestBody["network_policy"]) } + +func TestCreateClusterIncludesNetworkPolicy(t *testing.T) { + var requestBody map[string]interface{} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/v3/cluster", r.URL.Path) + require.NoError(t, json.NewDecoder(r.Body).Decode(&requestBody)) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(`{"cluster":{"id":"cluster-id"}}`)) + })) + defer server.Close() + + httpClient := platformclient.NewHTTPClient(server.URL, "fake-api-key") + client := &VendorV3Client{HTTPClient: *httpClient} + + _, ve, err := client.CreateCluster(CreateClusterOpts{ + Name: "test-cluster", + KubernetesDistribution: "k3s", + KubernetesVersion: "1.31", + NodeCount: 1, + NetworkPolicy: "airgap", + }) + require.NoError(t, err) + require.Nil(t, ve) + require.Equal(t, "test-cluster", requestBody["name"]) + require.Equal(t, "airgap", requestBody["network_policy"]) +}