func getKubeadmConfig(cfg *config.Cluster, data kubeadm.ConfigData, node nodes.Node, provider string) (path string, err error) {
	kubeVersion, err := nodeutils.KubeVersion(node)
	if err != nil {
		return "", errors.Wrap(err, "failed to get kubernetes version from node")
	}
	data.KubernetesVersion = kubeVersion
	var configNode *config.Node
	namer := common.MakeNodeNamer("")
	for i := range cfg.Nodes {
		n := &cfg.Nodes[i]
		nodeSuffix := namer(string(n.Role))
		if strings.HasSuffix(node.String(), nodeSuffix) {
			configNode = n
		}
	}
	if configNode == nil {
		return "", errors.Errorf("failed to match node %q to config", node.String())
	}
	nodeAddress, nodeAddressIPv6, err := node.IP()
	if err != nil {
		return "", errors.Wrap(err, "failed to get IP for node")
	}
	data.NodeAddress = nodeAddress
	if cfg.Networking.IPFamily == config.IPv6Family || cfg.Networking.IPFamily == config.DualStackFamily {
		if ip := net.ParseIP(nodeAddressIPv6); ip.To16() == nil {
			return "", errors.Errorf("failed to get IPv6 address for node %s; is %s configured to use IPv6 correctly?", node.String(), provider)
		}
		data.NodeAddress = nodeAddressIPv6
		if cfg.Networking.IPFamily == config.DualStackFamily {
			data.NodeAddress = fmt.Sprintf("%s,%s", nodeAddress, nodeAddressIPv6)
		}
	}
	cf, err := kubeadm.Config(data)
	if err != nil {
		return "", err
	}
	clusterPatches, clusterJSONPatches := allPatchesFromConfig(cfg)
	patchedConfig, err := patch.KubeYAML(cf, clusterPatches, clusterJSONPatches)
	if err != nil {
		return "", err
	}
	if len(configNode.KubeadmConfigPatches) > 0 || len(configNode.KubeadmConfigPatchesJSON6902) > 0 {
		patchedConfig, err = patch.KubeYAML(patchedConfig, configNode.KubeadmConfigPatches, configNode.KubeadmConfigPatchesJSON6902)
		if err != nil {
			return "", err
		}
	}
	return removeMetadata(patchedConfig), nil
}
getKubeadmConfig generates the kubeadm config contents for the cluster
by running data through the template and applying patches as needed.
package config
import (
	"bytes"
	"fmt"
	"net"
	"strings"
	"sigs.k8s.io/kind/pkg/cluster/constants"
	"sigs.k8s.io/kind/pkg/cluster/nodes"
	"sigs.k8s.io/kind/pkg/errors"
	"sigs.k8s.io/kind/pkg/cluster/internal/create/actions"
	"sigs.k8s.io/kind/pkg/cluster/internal/kubeadm"
	"sigs.k8s.io/kind/pkg/cluster/internal/patch"
	"sigs.k8s.io/kind/pkg/cluster/internal/providers/common"
	"sigs.k8s.io/kind/pkg/cluster/nodeutils"
	"sigs.k8s.io/kind/pkg/internal/apis/config"
)
type Action struct{}
func NewAction() actions.Action {
	return &Action{}
}
func (a *Action) Execute(ctx *actions.ActionContext) error {
	ctx.Status.Start("Writing configuration 📜")
	defer ctx.Status.End(false)
	providerInfo, err := ctx.Provider.Info()
	if err != nil {
		return err
	}
	allNodes, err := ctx.Nodes()
	if err != nil {
		return err
	}
	controlPlaneEndpoint, err := ctx.Provider.GetAPIServerInternalEndpoint(ctx.Config.Name)
	if err != nil {
		return err
	}
	fns := []func() error{}
	provider := fmt.Sprintf("%s", ctx.Provider)
	configData := kubeadm.ConfigData{
		NodeProvider:         provider,
		ClusterName:          ctx.Config.Name,
		ControlPlaneEndpoint: controlPlaneEndpoint,
		APIBindPort:          common.APIServerInternalPort,
		APIServerAddress:     ctx.Config.Networking.APIServerAddress,
		Token:                kubeadm.Token,
		PodSubnet:            ctx.Config.Networking.PodSubnet,
		KubeProxyMode:        string(ctx.Config.Networking.KubeProxyMode),
		ServiceSubnet:        ctx.Config.Networking.ServiceSubnet,
		ControlPlane:         true,
		IPv6:                 ctx.Config.Networking.IPFamily == config.IPv6Family,
		FeatureGates:         ctx.Config.FeatureGates,
		RuntimeConfig:        ctx.Config.RuntimeConfig,
		RootlessProvider:     providerInfo.Rootless,
	}
	kubeadmConfigPlusPatches := func(node nodes.Node, data kubeadm.ConfigData) func() error {
		return func() error {
			data.NodeName = node.String()
			kubeadmConfig, err := getKubeadmConfig(ctx.Config, data, node, provider)
			if err != nil {
				return errors.Wrap(err, "failed to generate kubeadm config content")
			}
			ctx.Logger.V(2).Infof("Using the following kubeadm config for node %s:\n%s", node.String(), kubeadmConfig)
			return writeKubeadmConfig(kubeadmConfig, node)
		}
	}
	controlPlaneLabels := []map[string]string{}
	workerLabels := []map[string]string{}
	for _, node := range ctx.Config.Nodes {
		if node.Role == config.ControlPlaneRole {
			controlPlaneLabels = append(controlPlaneLabels, node.Labels)
		} else if node.Role == config.WorkerRole {
			workerLabels = append(workerLabels, node.Labels)
		} else {
			continue
		}
	}
	hashMapLabelsToCommaSeparatedLabels := func(labels map[string]string) string {
		output := ""
		for key, value := range labels {
			output += fmt.Sprintf("%s=%s,", key, value)
		}
		return strings.TrimSuffix(output, ",")  
	}
	controlPlanes, err := nodeutils.ControlPlaneNodes(allNodes)
	if err != nil {
		return err
	}
	for i, node := range controlPlanes {
		node := node              
		configData := configData  
		if len(controlPlaneLabels[i]) > 0 {
			configData.NodeLabels = hashMapLabelsToCommaSeparatedLabels(controlPlaneLabels[i])  
		}
		fns = append(fns, kubeadmConfigPlusPatches(node, configData))
	}
	workers, err := nodeutils.SelectNodesByRole(allNodes, constants.WorkerNodeRoleValue)
	if err != nil {
		return err
	}
	if len(workers) > 0 {
		for i, node := range workers {
			node := node              
			configData := configData  
			configData.ControlPlane = false
			if len(workerLabels[i]) > 0 {
				configData.NodeLabels = hashMapLabelsToCommaSeparatedLabels(workerLabels[i])  
			}
			fns = append(fns, kubeadmConfigPlusPatches(node, configData))
		}
	}
	if err := errors.UntilErrorConcurrent(fns); err != nil {
		return err
	}
	if len(ctx.Config.ContainerdConfigPatches) > 0 || len(ctx.Config.ContainerdConfigPatchesJSON6902) > 0 {
		kubeNodes := append([]nodes.Node{}, controlPlanes...)
		kubeNodes = append(kubeNodes, workers...)
		fns := make([]func() error, len(kubeNodes))
		for i, node := range kubeNodes {
			node := node  
			fns[i] = func() error {
				const containerdConfigPath = "/etc/containerd/config.toml"
				var buff bytes.Buffer
				if err := node.Command("cat", containerdConfigPath).SetStdout(&buff).Run(); err != nil {
					return errors.Wrap(err, "failed to read containerd config from node")
				}
				patched, err := patch.TOML(buff.String(), ctx.Config.ContainerdConfigPatches, ctx.Config.ContainerdConfigPatchesJSON6902)
				if err != nil {
					return errors.Wrap(err, "failed to patch containerd config")
				}
				if err := nodeutils.WriteFile(node, containerdConfigPath, patched); err != nil {
					return errors.Wrap(err, "failed to write patched containerd config")
				}
				if err := node.Command("bash", "-c", `! systemctl is-system-running || systemctl restart containerd`).Run(); err != nil {
					return errors.Wrap(err, "failed to restart containerd after patching config")
				}
				return nil
			}
		}
		if err := errors.UntilErrorConcurrent(fns); err != nil {
			return err
		}
	}
	ctx.Status.End(true)
	return nil
}