package mapper import ( "fmt" "net" "strings" goVersion "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/edge/component/build" "edge-infra.dev/pkg/edge/constants" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/networking/netplan" ) var ( kubeadmAPIVersion = "kubeadm.k8s.io/v1beta3" controlPlaneEndpointPort = "6443" caHashFilePath = "/etc/kubernetes/pki/ca.crt" ) var ( apiEnabled = true gatewayEnabled = false ) var ( autoCompactionMode = "periodic" autoCompactionRetention = "5m" listenMetricsURLs = "http://0.0.0.0:2381" ) type TerminalBootstrapPayload struct { Terminal *model.Terminal EdgeInfraVersion string ClusterNetworkServices []*model.ClusterNetworkServiceInfo ShadowFileLine string GrubFileEntry string FirstNode bool BootstrapTokenValues []*model.KeyValues ClusterCaHash string BootstrapAck bool Organization string EdgeBootstrapToken string Endpoint string } func (p *TerminalBootstrapPayload) Yaml() (*types.TerminalBootstrapConfig, error) { ienNode := TerminalToIENode( p.Terminal, p.ClusterNetworkServices, make(map[string]string, 0), p.EdgeInfraVersion, ) tunnelSubnetCIDR, err := p.getTunnelSubnetCIDR() if err != nil { return &types.TerminalBootstrapConfig{}, err } netplan, err := netplan.GenerateTargetNetplanConfig( &ienNode, []corev1.Node{}, apiEnabled, gatewayEnabled, tunnelSubnetCIDR, ) if err != nil { return &types.TerminalBootstrapConfig{}, err } config := types.TerminalBootstrapConfig{} config.FirstNode = p.FirstNode config.GrubUser = constants.GrubRecoveryUser config.Hostname = ienNode.ObjectMeta.Name config.VipAddress = ienNode.Spec.KubeVip config.NtpServers = ienNode.Spec.NTPServers config.Role = p.Terminal.Role config.Netplan = netplan config.Registry = build.DefaultPublicContainerRegistry config.EdgeRegistries = types.EdgeRegistries{ Public: []string{build.DefaultPublicContainerRegistry}, Private: []string{build.EdgeWorkloadsRegistry, build.WorkloadsDirectory, build.WarehouseRegistry}, } config.GrubHash = p.GrubFileEntry config.BreakGlassHash = p.ShadowFileLine config.BootstrapAck = p.BootstrapAck config.Swap.Enabled = p.Terminal.SwapEnabled podSubnet := p.getClusterNetworkServiceIP(constants.ServiceTypePodNetworkCIDR) serviceSubnet := p.getClusterNetworkServiceIP(constants.ServiceTypeServiceNetworkCIDR) config.PodSubnet = podSubnet config.ServiceSubnet = serviceSubnet config.KubeadmConfig.KubeadmClusterConfig = *p.generateKubeadmClusterConfig(&config) //nolint:staticcheck // Allow existing usage of deprecated field // Deprecated: in 0.17: network fields will be returned as part of cluster config config.Network = types.Network{ //nolint:staticcheck // Allow existing usage of deprecated type ServiceSubnetCIDR: serviceSubnet, PodSubnetCIDR: podSubnet, } if config.FirstNode { config.NcrEdge = types.NcrEdge{ EdgeInfraVersion: p.EdgeInfraVersion, NcrEdgeParams: types.NcrEdgeParams{ ClusterEdgeID: p.Terminal.ClusterEdgeID, Organization: p.Organization, UploadCaHash: caHashFilePath, APIEndpoint: p.Endpoint, Token: p.EdgeBootstrapToken, }, BootstrapPayload: types.BootstrapPayload{ ClusterEdgeID: p.Terminal.ClusterEdgeID, }, } config.KubeadmConfig.KubeadmInitConfig = p.generateKubeadmInitConfig() //nolint:staticcheck // Allow existing usage of deprecated field } else { config.BootstrapInfo = types.BootstrapInfo{ ClusterCaHash: p.ClusterCaHash, JoinToken: p.BootstrapTokenValues[0].Value + "." + p.BootstrapTokenValues[1].Value, } config.KubeadmConfig.KubeadmJoinConfig = p.generateKubeadmJoinConfig(&config) //nolint:staticcheck // Allow existing usage of deprecated field } if p.Terminal.Disks != nil { disks := []types.Disks{} for _, disk := range p.Terminal.Disks { disks = append(disks, types.Disks{ DevicePath: disk.DevicePath, ExpectEmpty: disk.ExpectEmpty, IncludeDisk: disk.IncludeDisk, UsePart: disk.UsePart, }) } if p.Terminal.DiscoverDisks == nil { return &types.TerminalBootstrapConfig{}, fmt.Errorf( "unable to get terminal bootstrap as discover disks not set", ) } if p.Terminal.BootDisk != nil && *p.Terminal.BootDisk == "" { p.Terminal.BootDisk = nil } config.DiskOptions = types.DiskOptions{ PersistentVolume: types.PersistentVolume{ Disks: disks, DiscoverDisks: *p.Terminal.DiscoverDisks, BootDisk: p.Terminal.BootDisk, ExistingEfiPart: p.Terminal.ExistingEfiPart, }, } } minimumVersion, _ := goVersion.NewVersion("v0.17.0") currentVersion, _ := goVersion.NewVersion(p.EdgeInfraVersion) if currentVersion.LessThan(minimumVersion) { return &config, nil } if p.Terminal.PrimaryInterface == nil { return &types.TerminalBootstrapConfig{}, fmt.Errorf("unable to get terminal bootstrap as primary interface not set") } iface, err := p.getPrimaryTerminalInterface() if err != nil { return &types.TerminalBootstrapConfig{}, err } config.PrimaryInterface = types.PrimaryInterface{ ID: *p.Terminal.PrimaryInterface, Macs: []string{iface.MacAddress}, } return &config, nil } // Deprecated: DEPRECATED in 0.19 func (p *TerminalBootstrapPayload) generateKubeadmClusterConfig( config *types.TerminalBootstrapConfig, ) *types.KubeadmClusterConfig { return &types.KubeadmClusterConfig{ APIVersion: kubeadmAPIVersion, ImageRepository: "IMAGE_REPOSITORY", Kind: "ClusterConfiguration", KubernetesVersion: "KUBE_VERSION", ControlPlaneEndpoint: config.VipAddress + ":" + controlPlaneEndpointPort, Networking: types.KubeadmNetworkingConfig{ ServiceSubnet: p.getClusterNetworkServiceIP(constants.ServiceTypeServiceNetworkCIDR), PodSubnet: p.getClusterNetworkServiceIP(constants.ServiceTypePodNetworkCIDR), }, Etcd: p.getClusterEtcdConfig(), } } func (p *TerminalBootstrapPayload) getClusterEtcdConfig() types.KubeadmEtcdConfig { return types.KubeadmEtcdConfig{ Local: types.KubeadmEtcdLocal{ ExtraArgs: types.KubeadmEtcdExtraArgs{ AutoCompactionMode: autoCompactionMode, AutoCompactionRetention: autoCompactionRetention, ListenMetricsURLs: listenMetricsURLs, }, }, } } func (p *TerminalBootstrapPayload) generateKubeadmInitConfig() *types.KubeadmInitConfig { return &types.KubeadmInitConfig{ APIVersion: kubeadmAPIVersion, Kind: "InitConfiguration", NodeRegistration: types.KubeadmNodeRegistration{ Taints: []string{}, }, } } func (p *TerminalBootstrapPayload) generateKubeadmJoinConfig( config *types.TerminalBootstrapConfig, ) *types.KubeadmJoinConfig { clusterCaHash := p.ClusterCaHash if !strings.HasPrefix(p.ClusterCaHash, "sha256:") { clusterCaHash = "sha256:" + p.ClusterCaHash } return &types.KubeadmJoinConfig{ APIVersion: kubeadmAPIVersion, Kind: "JoinConfiguration", Discovery: types.KubeadmDiscovery{ BootstrapToken: types.KubeadmBootstrapToken{ Token: p.BootstrapTokenValues[0].Value + "." + p.BootstrapTokenValues[1].Value, APIServerEndpoint: config.VipAddress + ":" + controlPlaneEndpointPort, CaCertHashes: []string{clusterCaHash}, }, }, NodeRegistration: types.KubeadmNodeRegistration{ Taints: []string{}, }, } } func (p *TerminalBootstrapPayload) getTunnelSubnetCIDR() (*net.IPNet, error) { tunnelSubnetCIDR := p.getClusterNetworkServiceIP(constants.ServiceTypeEgressTunnelsCIDR) _, parsedTunnelCIDR, err := net.ParseCIDR(tunnelSubnetCIDR) if err != nil { return nil, err } return parsedTunnelCIDR, nil } func (p *TerminalBootstrapPayload) getClusterNetworkServiceIP(serviceType string) string { for _, service := range p.ClusterNetworkServices { if service.ServiceType == serviceType { return service.IP } } return constants.ClusterNetworkServiceDefaults[serviceType] } func (p *TerminalBootstrapPayload) getPrimaryTerminalInterface() (*model.TerminalInterface, error) { for _, iface := range p.Terminal.Interfaces { if iface.TerminalInterfaceID == *p.Terminal.PrimaryInterface { return iface, nil } } return nil, fmt.Errorf( "no interface with configured PrimaryInterface ID %s", *p.Terminal.PrimaryInterface, ) }