Files
Olares/cli/pkg/common/kube_runtime.go
Eliott van Nuffel df38148149
Some checks failed
Native Verification / Build Docs (pull_request) Successful in 1m12s
Native Verification / Build App-Service Native (pull_request) Successful in 1m27s
Native Verification / Build Daemon Native (pull_request) Successful in 42s
Lint and Test Charts / lint-test (pull_request_target) Failing after 19s
Lint and Test Charts / test-version (pull_request_target) Successful in 0s
Lint and Test Charts / push-image (pull_request_target) Failing after 15s
Lint and Test Charts / upload-cli (pull_request_target) Failing after 1m15s
Lint and Test Charts / upload-daemon (pull_request_target) Failing after 1m12s
Lint and Test Charts / push-deps (pull_request_target) Has been skipped
Lint and Test Charts / push-deps-arm64 (pull_request_target) Has been skipped
Lint and Test Charts / push-image-arm64 (pull_request_target) Has been cancelled
Lint and Test Charts / upload-package (pull_request_target) Has been cancelled
Lint and Test Charts / install-test (pull_request_target) Has been cancelled
continue beOS rebrand and add native verification
2026-03-10 13:48:45 +01:00

515 lines
16 KiB
Go
Executable File

/*
Copyright 2021 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/joho/godotenv"
"github.com/pkg/errors"
"github.com/spf13/viper"
kubekeyapiv1alpha2 "github.com/beclab/beos/cli/apis/kubekey/v1alpha2"
"github.com/beclab/beos/cli/pkg/core/common"
"github.com/beclab/beos/cli/pkg/core/connector"
"github.com/beclab/beos/cli/pkg/core/logger"
"github.com/beclab/beos/cli/pkg/core/util"
kresource "k8s.io/apimachinery/pkg/api/resource"
)
type KubeRuntime struct {
connector.BaseRuntime
Cluster *kubekeyapiv1alpha2.ClusterSpec
Arg *Argument
}
type Argument struct {
KubernetesVersion string `json:"kubernetes_version"`
BeOSVersion string `json:"olares_version"`
SecurityEnhancement bool `json:"security_enhancement"`
InCluster bool `json:"in_cluster"`
ContainerManager string `json:"container_manager"`
Kubetype string `json:"kube_type"`
SystemInfo connector.Systems
RegistryMirrors string `json:"registry_mirrors"`
BeOSCDNService string `json:"olares_cdn_service"`
// Swap config
*SwapConfig
// master node ssh config
*MasterHostConfig
// User
User *User `json:"user"`
// if juicefs is opted off, the local storage is used directly
// only used in prepare phase
// the existence of juicefs should be checked in other phases
// to avoid wrong information given by user
WithJuiceFS bool `json:"with_juicefs"`
// the object storage service used as backend for JuiceFS
Storage *Storage `json:"storage"`
NetworkSettings *NetworkSettings `json:"network_settings"`
GPU *GPU `json:"gpu"`
IsCloudInstance bool `json:"is_cloud_instance"`
MinikubeProfile string `json:"minikube_profile"`
WSLDistribution string `json:"wsl_distribution"`
Environment []string `json:"environment"`
BaseDir string `json:"base_dir"`
Manifest string `json:"manifest"`
ConsoleLogFileName string `json:"console_log_file_name"`
ConsoleLogTruncate bool `json:"console_log_truncate"`
HostIP string `json:"host_ip"`
IsBeOSInContainer bool `json:"is_olares_in_container"`
}
type SwapConfig struct {
EnablePodSwap bool `json:"enable_pod_swap"`
Swappiness int `json:"swappiness"`
EnableZRAM bool `json:"enable_zram"`
ZRAMSize string `json:"zram_size"`
ZRAMSwapPriority int `json:"zram_swap_priority"`
}
func (cfg *SwapConfig) Validate() error {
if cfg.ZRAMSize == "" {
return nil
}
processedZRAMSize := cfg.ZRAMSize
if strings.HasSuffix(processedZRAMSize, "b") || strings.HasSuffix(processedZRAMSize, "B") {
processedZRAMSize = strings.TrimSuffix(cfg.ZRAMSize, "b")
processedZRAMSize = strings.TrimSuffix(cfg.ZRAMSize, "B")
}
processedZRAMSize = strings.ReplaceAll(processedZRAMSize, "g", "G")
processedZRAMSize = strings.ReplaceAll(processedZRAMSize, "k", "K")
processedZRAMSize = strings.ReplaceAll(processedZRAMSize, "m", "M")
q, err := kresource.ParseQuantity(processedZRAMSize)
if err != nil {
return fmt.Errorf("invalid zram size %s: %w", cfg.ZRAMSize, err)
}
cfg.ZRAMSize = q.String() + "B"
return nil
}
type MasterHostConfig struct {
MasterHost string `json:"master_host"`
MasterNodeName string `json:"master_node_name"`
MasterSSHUser string `json:"master_ssh_user"`
MasterSSHPassword string `json:"master_ssh_password"`
MasterSSHPrivateKeyPath string `json:"master_ssh_private_key_path"`
MasterSSHPort int `json:"master_ssh_port"`
}
func (cfg *MasterHostConfig) Validate() error {
if cfg.MasterHost == "" {
return errors.New("master host is not provided")
}
if cfg.MasterSSHUser != "" && cfg.MasterSSHUser != "root" && cfg.MasterSSHPassword == "" {
return errors.New("master ssh password must be provided for non-root user in order to execute sudo command")
}
return nil
}
type NetworkSettings struct {
// OSPublicIPs contains a list of public ip(s)
// by looking at local network interfaces
// if any
OSPublicIPs []net.IP `json:"os_public_ips"`
// CloudProviderPublicIP contains the info retrieved from the cloud provider instance metadata service
// if any
CloudProviderPublicIP net.IP `json:"cloud_provider_public_ip"`
// ExternalPublicIP is the IP address seen by others on the internet
// it may not be an IP address
// that's directly bound to a local network interface, e.g. on an AWS EC2 instance
// or may not be an IP address
// that can be used to access the machine at all, e.g. a machine behind multiple NAT gateways
// this is used as a fallback method to determine the machine's public IP address
// if none can be found from the OS or AWS IMDS service
// but the user explicitly specifies that the machine is publicly accessible
ExternalPublicIP net.IP `json:"external_public_ip"`
EnableReverseProxy *bool `json:"enable_reverse_proxy"`
}
type User struct {
UserName string `json:"user_name"`
Password string `json:"user_password"`
EncryptedPassword string `json:"-"`
Email string `json:"user_email"`
DomainName string `json:"user_domain_name"`
}
type Storage struct {
StorageVendor string `json:"storage_vendor"`
StorageType string `json:"storage_type"`
StorageBucket string `json:"storage_bucket"`
StoragePrefix string `json:"storage_prefix"`
StorageAccessKey string `json:"storage_access_key"`
StorageSecretKey string `json:"storage_secret_key"`
StorageToken string `json:"storage_token"` // juicefs
StorageClusterId string `json:"storage_cluster_id"` // use only on the Terminus cloud, juicefs
StorageSyncSecret string `json:"storage_sync_secret"` // use only on the Terminus cloud
BackupClusterBucket string `json:"backup_cluster_bucket"`
}
type GPU struct {
Enable bool `json:"gpu_enable"`
}
func NewArgument() *Argument {
si := connector.GetSystemInfo()
arg := &Argument{
ContainerManager: Containerd,
SystemInfo: si,
Storage: &Storage{
StorageType: ManagedMinIO,
},
GPU: &GPU{},
User: &User{
UserName: strings.TrimSpace(viper.GetString(FlagOSUserName)),
DomainName: strings.TrimSpace(viper.GetString(FlagOSDomainName)),
Password: strings.TrimSpace(viper.GetString(FlagOSPassword)),
},
NetworkSettings: &NetworkSettings{},
RegistryMirrors: viper.GetString(FlagRegistryMirrors),
BeOSCDNService: viper.GetString(FlagCDNService),
HostIP: viper.GetString(ENV_HOST_IP),
Environment: os.Environ(),
MasterHostConfig: &MasterHostConfig{},
SwapConfig: &SwapConfig{},
}
// default enable GPU unless explicitly set to "0"
arg.GPU.Enable = !strings.EqualFold(os.Getenv(ENV_LOCAL_GPU_ENABLE), "0")
arg.IsCloudInstance, _ = strconv.ParseBool(os.Getenv(ENV_TERMINUS_IS_CLOUD_VERSION))
arg.IsBeOSInContainer = os.Getenv(ENV_CONTAINER_MODE) == "oic"
si.IsOIC = arg.IsBeOSInContainer
si.ProductName = arg.GetProductName()
// Ensure BaseDir is initialized before loading master.conf
// so master host config can be loaded from ${base-dir}/master.conf reliably.
arg.SetBaseDir(viper.GetString(FlagBaseDir))
arg.loadMasterHostConfig()
return arg
}
func (a *Argument) SaveReleaseInfo(withoutName bool) error {
if a.BaseDir == "" {
return errors.New("invalid: empty base directory")
}
if a.BeOSVersion == "" {
return errors.New("invalid: empty olares version")
}
releaseInfoMap := map[string]string{
ENV_OLARES_BASE_DIR: a.BaseDir,
ENV_OLARES_VERSION: a.BeOSVersion,
}
if !withoutName {
if a.User != nil && a.User.UserName != "" && a.User.DomainName != "" {
releaseInfoMap["OLARES_NAME"] = fmt.Sprintf("%s@%s", a.User.UserName, a.User.DomainName)
} else {
if util.IsExist(BeOSReleaseFile) {
// if the user is not set, try to load the user name from the release file
envs, err := godotenv.Read(BeOSReleaseFile)
if err == nil {
if userName, ok := envs["OLARES_NAME"]; ok {
releaseInfoMap["OLARES_NAME"] = userName
}
}
}
}
}
if !util.IsExist(filepath.Dir(BeOSReleaseFile)) {
if err := os.MkdirAll(filepath.Dir(BeOSReleaseFile), 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %v", filepath.Dir(BeOSReleaseFile), err)
}
}
return godotenv.Write(releaseInfoMap, BeOSReleaseFile)
}
func (a *Argument) GetWslUserPath() string {
if a.Environment == nil || len(a.Environment) == 0 {
return ""
}
var res string
var wslSuffix = "/AppData/Local/Microsoft/WindowsApps"
for _, v := range a.Environment {
if strings.HasPrefix(v, "PATH=") {
p := strings.ReplaceAll(v, "PATH=", "")
s := strings.Split(p, ":")
for _, s1 := range s {
if strings.Contains(s1, wslSuffix) {
res = strings.ReplaceAll(s1, wslSuffix, "")
break
}
}
}
}
return res
}
func (a *Argument) SetBeOSCDNService(url string) {
u := strings.TrimSuffix(url, "/")
if u == "" {
u = common.DefaultBeOSCDNService
}
a.BeOSCDNService = u
}
func (a *Argument) SetGPU(enable bool) {
if a.GPU == nil {
a.GPU = new(GPU)
}
a.GPU.Enable = enable
}
func (a *Argument) SetBeOSVersion(version string) {
if version == "" || len(version) <= 2 {
return
}
if version[0] == 'v' {
version = version[1:]
}
a.BeOSVersion = version
}
func (a *Argument) SetStorage(storage *Storage) {
a.Storage = storage
}
func (a *Argument) SetMinikubeProfile(profile string) {
a.MinikubeProfile = profile
if profile == "" && a.SystemInfo.IsDarwin() {
fmt.Printf("\nNote: Minikube profile is not set, will try to use the default profile: \"%s\"\n", MinikubeDefaultProfile)
fmt.Println("if this is not expected, please specify it explicitly by setting the --profile/-p option\n")
a.MinikubeProfile = MinikubeDefaultProfile
}
}
func (a *Argument) SetWSLDistribution(distribution string) {
a.WSLDistribution = distribution
if distribution == "" && a.SystemInfo.IsWindows() {
fmt.Printf("\nNote: WSL distribution is not set, will try to use the default distribution: \"%s\"\n", WSLDefaultDistribution)
fmt.Println("if this is not expected, please specify it explicitly by setting the --distribution/-d option\n")
a.WSLDistribution = WSLDefaultDistribution
}
}
func (a *Argument) SetKubeVersion(kubeType string) {
var kubeVersion = DefaultK3sVersion
if kubeType == K8s {
kubeVersion = DefaultK8sVersion
}
a.KubernetesVersion = kubeVersion
a.Kubetype = kubeType
}
func (a *Argument) SetBaseDir(dir string) {
if dir != "" {
a.BaseDir = dir
}
if a.BaseDir == "" {
a.BaseDir = filepath.Join(a.SystemInfo.GetHomeDir(), common.DefaultBaseDir)
}
if !filepath.IsAbs(a.BaseDir) {
var err error
var absBaseDir string
absBaseDir, err = filepath.Abs(a.BaseDir)
if err != nil {
panic(fmt.Errorf("failed to get absolute path for base directory %s: %v", a.BaseDir, err))
}
a.BaseDir = absBaseDir
}
}
// loadMasterHostConfig loads master host configuration from master.conf file (if exists)
// and then overrides with any values set via command line flags or environment variables.
func (a *Argument) loadMasterHostConfig() {
// First, try to load from master.conf file
configPath := filepath.Join(a.BaseDir, MasterHostConfigFile)
if content, err := os.ReadFile(configPath); err == nil {
json.Unmarshal(content, a.MasterHostConfig)
}
// Then override with viper values (from flags or env)
if v := viper.GetString(FlagMasterHost); v != "" {
a.MasterHost = v
}
if v := viper.GetString(FlagMasterNodeName); v != "" {
a.MasterNodeName = v
}
if v := viper.GetString(FlagMasterSSHUser); v != "" {
a.MasterSSHUser = v
}
if v := viper.GetString(FlagMasterSSHPassword); v != "" {
a.MasterSSHPassword = v
}
if v := viper.GetString(FlagMasterSSHPrivateKeyPath); v != "" {
a.MasterSSHPrivateKeyPath = v
}
if v := viper.GetInt(FlagMasterSSHPort); v != 0 {
a.MasterSSHPort = v
}
// Set a dummy name to bypass validity checks if master host is set but node name is not
if a.MasterHost != "" && a.MasterNodeName == "" {
a.MasterNodeName = "master"
}
}
func (a *Argument) ClearMasterHostConfig() {
a.MasterHostConfig = &MasterHostConfig{}
}
func (a *Argument) SetManifest(manifest string) {
a.Manifest = manifest
}
func (a *Argument) SetConsoleLog(fileName string, truncate bool) {
a.ConsoleLogFileName = fileName
a.ConsoleLogTruncate = truncate
}
func (a *Argument) SetSwapConfig(config SwapConfig) {
a.SwapConfig = &SwapConfig{}
if config.ZRAMSize != "" || config.ZRAMSwapPriority != 0 {
a.EnableZRAM = true
} else {
a.EnableZRAM = config.EnableZRAM
}
if a.EnableZRAM {
a.ZRAMSize = config.ZRAMSize
a.ZRAMSwapPriority = config.ZRAMSwapPriority
a.EnablePodSwap = true
} else {
a.EnablePodSwap = config.EnablePodSwap
}
a.Swappiness = config.Swappiness
}
func (a *Argument) SetMasterHostOverride(config MasterHostConfig) {
if config.MasterHost != "" {
a.MasterHost = config.MasterHost
}
if config.MasterNodeName != "" {
a.MasterNodeName = config.MasterNodeName
}
// set a dummy name to bypass validity checks
// as it will be overridden later when the node name is fetched
if a.MasterNodeName == "" {
a.MasterNodeName = "master"
}
if config.MasterSSHPassword != "" {
a.MasterSSHPassword = config.MasterSSHPassword
}
if config.MasterSSHUser != "" {
a.MasterSSHUser = config.MasterSSHUser
}
if config.MasterSSHPort != 0 {
a.MasterSSHPort = config.MasterSSHPort
}
if config.MasterSSHPrivateKeyPath != "" {
a.MasterSSHPrivateKeyPath = config.MasterSSHPrivateKeyPath
}
}
func (a *Argument) LoadMasterHostConfigIfAny() error {
if a.BaseDir == "" {
return errors.New("basedir unset")
}
content, err := os.ReadFile(filepath.Join(a.BaseDir, MasterHostConfigFile))
if os.IsNotExist(err) {
return nil
}
if err != nil {
return err
}
return json.Unmarshal(content, a.MasterHostConfig)
}
func (a *Argument) GetProductName() string {
data, err := os.ReadFile("/sys/class/dmi/id/product_name")
if err != nil {
fmt.Printf("\nCannot get product name on this device, %s\n", err)
return ""
}
return strings.TrimSpace(string(data))
}
func NewKubeRuntime(arg Argument) (*KubeRuntime, error) {
loader := NewLoader(arg)
cluster, err := loader.Load()
if err != nil {
return nil, err
}
base := connector.NewBaseRuntime(cluster.Name, connector.NewDialer(),
arg.BaseDir, arg.BeOSVersion, arg.ConsoleLogFileName, arg.ConsoleLogTruncate, arg.SystemInfo)
clusterSpec := &cluster.Spec
defaultCluster, roleGroups := clusterSpec.SetDefaultClusterSpec(arg.InCluster, arg.SystemInfo.IsDarwin())
hostSet := make(map[string]struct{})
for _, role := range roleGroups {
for _, host := range role {
if host.IsRole(Master) || host.IsRole(Worker) {
host.SetRole(K8s)
}
if _, ok := hostSet[host.GetName()]; !ok {
hostSet[host.GetName()] = struct{}{}
base.AppendHost(host)
base.AppendRoleMap(host)
}
host.SetOs(arg.SystemInfo.GetOsType())
host.SetMinikubeProfile(arg.MinikubeProfile)
}
}
args, _ := json.Marshal(arg)
logger.Debugf("[runtime] arg: %s", string(args))
r := &KubeRuntime{
Cluster: defaultCluster,
Arg: &arg,
}
r.BaseRuntime = base
return r, nil
}
// Copy is used to create a copy for Runtime.
func (k *KubeRuntime) Copy() connector.Runtime {
runtime := *k
return &runtime
}