Skip to content

Commit

Permalink
Enable Single Node Cluster deployment via tanzu CLI (vmware-tanzu#3674)
Browse files Browse the repository at this point in the history
Co-authored-by: Jamie Monserrate <monserratej@vmware.com>

Co-authored-by: Jamie Monserrate <monserratej@vmware.com>
  • Loading branch information
Ankitasw and jamiemonserrate committed Nov 9, 2022
1 parent c84836a commit 2f9044a
Show file tree
Hide file tree
Showing 31 changed files with 1,222 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ spec:
type: string
default: []
- name: worker
required: true
required: false
schema:
openAPIV3Schema:
type: object
Expand Down Expand Up @@ -457,6 +457,12 @@ spec:
openAPIV3Schema:
type: object
properties: {}
- name: controlPlaneTaint
required: false
schema:
openAPIV3Schema:
type: boolean
default: true
patches:
- name: vsphereClusterTemplate
definitions:
Expand Down Expand Up @@ -1402,6 +1408,21 @@ spec:
- level: Metadata
omitStages:
- "RequestReceived"
- name: controlPlaneTaint
enabledIf: '{{ not .controlPlaneTaint }}'
definitions:
- selector:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
matchResources:
controlPlane: true
jsonPatches:
- op: add
path: /spec/template/spec/kubeadmConfigSpec/initConfiguration/nodeRegistration/taints
value: []
- op: add
path: /spec/template/spec/kubeadmConfigSpec/joinConfiguration/nodeRegistration/taints
value: []
- name: windows
definitions:
- selector:
Expand Down
23 changes: 22 additions & 1 deletion providers/infrastructure-vsphere/v1.4.1/cconly/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ spec:
type: string
default: []
- name: worker
required: true
required: false
schema:
openAPIV3Schema:
type: object
Expand Down Expand Up @@ -457,6 +457,12 @@ spec:
openAPIV3Schema:
type: object
properties: {}
- name: controlPlaneTaint
required: false
schema:
openAPIV3Schema:
type: boolean
default: true
patches:
- name: vsphereClusterTemplate
definitions:
Expand Down Expand Up @@ -1402,6 +1408,21 @@ spec:
- level: Metadata
omitStages:
- "RequestReceived"
- name: controlPlaneTaint
enabledIf: '{{ not .controlPlaneTaint }}'
definitions:
- selector:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
matchResources:
controlPlane: true
jsonPatches:
- op: add
path: /spec/template/spec/kubeadmConfigSpec/initConfiguration/nodeRegistration/taints
value: []
- op: add
path: /spec/template/spec/kubeadmConfigSpec/joinConfiguration/nodeRegistration/taints
value: []
- name: windows
definitions:
- selector:
Expand Down
3 changes: 3 additions & 0 deletions tkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ type TkgClient struct {
tkgConfigPathsClient tkgconfigpaths.Client
clusterKubeConfig *types.ClusterKubeConfig
clusterClientFactory clusterclient.ClusterClientFactory
vcClientFactory vc.VcClientFactory
featureFlagClient FeatureFlagClient
}

Expand All @@ -253,6 +254,7 @@ type Options struct {
TKGPathsClient tkgconfigpaths.Client
ClusterKubeConfig *types.ClusterKubeConfig
ClusterClientFactory clusterclient.ClusterClientFactory
VcClientFactory vc.VcClientFactory
FeatureFlagClient FeatureFlagClient
}

Expand Down Expand Up @@ -283,6 +285,7 @@ func New(options Options) (*TkgClient, error) { // nolint:gocritic
tkgConfigPathsClient: options.TKGPathsClient,
clusterKubeConfig: options.ClusterKubeConfig,
clusterClientFactory: options.ClusterClientFactory,
vcClientFactory: options.VcClientFactory,
featureFlagClient: options.FeatureFlagClient,
}, nil
}
Expand Down
74 changes: 73 additions & 1 deletion tkg/client/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@
package client

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"

"gopkg.in/yaml.v3"

"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/version"
capi "sigs.k8s.io/cluster-api/api/v1beta1"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
clusterctlclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
Expand All @@ -25,6 +31,7 @@ import (
"github.com/vmware-tanzu/tanzu-framework/tkg/log"
"github.com/vmware-tanzu/tanzu-framework/tkg/tkgconfighelper"
"github.com/vmware-tanzu/tanzu-framework/tkg/utils"
"github.com/vmware-tanzu/tanzu-framework/util/topology"
)

const (
Expand Down Expand Up @@ -95,7 +102,7 @@ func (c *TkgClient) CreateCluster(options *CreateClusterOptions, waitForCluster
GetClientTimeout: 3 * time.Second,
OperationTimeout: c.timeout,
}
regionalClusterClient, err := clusterclient.NewClient(options.Kubeconfig.Path, options.Kubeconfig.Context, clusterclientOptions)
regionalClusterClient, err := c.clusterClientFactory.NewClient(options.Kubeconfig.Path, options.Kubeconfig.Context, clusterclientOptions)
if err != nil {
return false, errors.Wrap(err, "unable to get cluster client while creating cluster")
}
Expand Down Expand Up @@ -141,6 +148,11 @@ func (c *TkgClient) CreateCluster(options *CreateClusterOptions, waitForCluster
if err != nil {
return false, errors.Wrap(err, "unable to get cluster configuration")
}

err = validateConfigForSingleNodeCluster(bytes, options, c)
if err != nil {
return false, err
}
} else {
bytes, err = c.getClusterConfigurationBytes(&options.ClusterConfigOptions, infraProviderName, isManagementCluster, options.IsWindowsWorkloadCluster)
if err != nil {
Expand Down Expand Up @@ -862,3 +874,63 @@ func (c *TkgClient) ValidateManagementClusterVersionWithCLI(regionalClusterClien

return nil
}

// validateConfigForSingleNodeCluster validates that the controlPlaneTaint CC variable is not set for the single node workload cluster, otherwise returns error
func validateConfigForSingleNodeCluster(stream []byte, options *CreateClusterOptions, tkgClient *TkgClient) error {
cluster, err := getClusterObjectFromYaml(stream)
if err != nil {
return err
}

if !topology.IsSingleNodeCluster(cluster) {
return nil
}

controlPlaneTaint := true
err = topology.GetVariable(cluster, "controlPlaneTaint", &controlPlaneTaint)
if err != nil {
return errors.Wrap(err, "failed to get CC variable controlPlaneTaint")
}

// Do not allow the creation of single node clusters without the feature gate.
if !tkgClient.IsFeatureActivated(constants.FeatureFlagSingleNodeClusters) {
return errors.New("Worker count cannot be 0, minimum worker count required is 1")
}

if controlPlaneTaint {
return errors.New(fmt.Sprintf("unable to create single node cluster %s as control plane node has taint", options.ClusterName))
}

return nil
}

func getClusterObjectFromYaml(stream []byte) (*capi.Cluster, error) {
clusterYaml, err := findClusterDefinitionIn(stream)
if err != nil {
return nil, err
}

cluster := &capi.Cluster{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(clusterYaml, cluster)
if err != nil {
return nil, errors.Wrap(err, "unable to convert yaml to structured format")
}
return cluster, nil
}

func findClusterDefinitionIn(stream []byte) (map[string]interface{}, error) {
clusterYaml := make(map[string]interface{})
decoder := yaml.NewDecoder(bytes.NewBufferString(string(stream)))
for {
if err := decoder.Decode(&clusterYaml); err != nil {
if err == io.EOF {
break
}
return clusterYaml, errors.Wrap(err, "unable to read cluster yaml")
}
if clusterYaml["kind"] == constants.KindCluster {
break
}
}
return clusterYaml, nil
}
Loading

0 comments on commit 2f9044a

Please sign in to comment.