mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-27 17:41:17 +02:00
331 lines
8.9 KiB
Go
331 lines
8.9 KiB
Go
package userdata
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/autonomy/talos/internal/pkg/crypto/x509"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
|
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// Env represents a set of environment variables.
|
|
type Env = map[string]string
|
|
|
|
// UserData represents the user data.
|
|
type UserData struct {
|
|
Version string `yaml:"version"`
|
|
Security *Security `yaml:"security"`
|
|
Networking *Networking `yaml:"networking"`
|
|
Services *Services `yaml:"services"`
|
|
Files []*File `yaml:"files"`
|
|
Debug bool `yaml:"debug"`
|
|
Env Env `yaml:"env,omitempty"`
|
|
Install *Install `yaml:"install,omitempty"`
|
|
}
|
|
|
|
// Security represents the set of options available to configure security.
|
|
type Security struct {
|
|
OS *OSSecurity `yaml:"os"`
|
|
Kubernetes *KubernetesSecurity `yaml:"kubernetes"`
|
|
}
|
|
|
|
// OSSecurity represents the set of security options specific to the OS.
|
|
type OSSecurity struct {
|
|
CA *x509.PEMEncodedCertificateAndKey `yaml:"ca"`
|
|
Identity *x509.PEMEncodedCertificateAndKey `yaml:"identity"`
|
|
}
|
|
|
|
// KubernetesSecurity represents the set of security options specific to
|
|
// Kubernetes.
|
|
type KubernetesSecurity struct {
|
|
CA *x509.PEMEncodedCertificateAndKey `yaml:"ca"`
|
|
}
|
|
|
|
// Networking represents the set of options available to configure networking.
|
|
type Networking struct {
|
|
OS struct{} `yaml:"os"`
|
|
Kubernetes struct{} `yaml:"kubernetes"`
|
|
}
|
|
|
|
// Services represents the set of services available to configure.
|
|
type Services struct {
|
|
Init *Init `yaml:"init"`
|
|
Kubelet *Kubelet `yaml:"kubelet"`
|
|
Kubeadm *Kubeadm `yaml:"kubeadm"`
|
|
Trustd *Trustd `yaml:"trustd"`
|
|
Proxyd *Proxyd `yaml:"proxyd"`
|
|
Blockd *Blockd `yaml:"blockd"`
|
|
OSD *OSD `yaml:"osd"`
|
|
CRT *CRT `yaml:"crt"`
|
|
}
|
|
|
|
// File represents a files to write to disk.
|
|
type File struct {
|
|
Contents string `yaml:"contents"`
|
|
Permissions os.FileMode `yaml:"permissions"`
|
|
Path string `yaml:"path"`
|
|
}
|
|
|
|
// Install represents the installation options for preparing a node
|
|
type Install struct {
|
|
Boot *InstallDevice `yaml:"boot,omitempty"`
|
|
Root *InstallDevice `yaml:"root"`
|
|
Data *InstallDevice `yaml:"data,omitempty"`
|
|
Wipe bool `yaml:"wipe"`
|
|
}
|
|
|
|
// InstallDevice represents the specific directions for each partition
|
|
type InstallDevice struct {
|
|
Device string `yaml:"device,omitempty"`
|
|
Size uint `yaml:"size,omitempty"`
|
|
Data []string `yaml:"data,omitempty"`
|
|
}
|
|
|
|
// Init describes the configuration of the init service.
|
|
type Init struct {
|
|
CNI string `yaml:"cni,omitempty"`
|
|
}
|
|
|
|
// Kubelet describes the configuration of the kubelet service.
|
|
type Kubelet struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
}
|
|
|
|
// Kubeadm describes the set of configuration options available for kubeadm.
|
|
type Kubeadm struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
|
|
Configuration runtime.Object `yaml:"configuration"`
|
|
bootstrap bool
|
|
controlPlane bool
|
|
}
|
|
|
|
// MarshalYAML implements the yaml.Marshaler interface.
|
|
func (kdm *Kubeadm) MarshalYAML() (interface{}, error) {
|
|
var aux struct {
|
|
Configuration string `yaml:"configuration,omitempty"`
|
|
}
|
|
|
|
b, err := configutil.MarshalKubeadmConfigObject(kdm.Configuration)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) {
|
|
kdm.bootstrap = true
|
|
}
|
|
if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
|
|
kdm.bootstrap = false
|
|
}
|
|
|
|
aux.Configuration = string(b)
|
|
|
|
return aux, nil
|
|
}
|
|
|
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var aux struct {
|
|
Configuration string `yaml:"configuration,omitempty"`
|
|
}
|
|
|
|
if err := unmarshal(&aux); err != nil {
|
|
return err
|
|
}
|
|
|
|
b := []byte(aux.Configuration)
|
|
|
|
gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) {
|
|
// Since the ClusterConfiguration is embedded in the InitConfiguration
|
|
// struct, it is required to (un)marshal it a special way. The kubeadm
|
|
// API exposes one function (MarshalKubeadmConfigObject) to handle the
|
|
// marshaling, but does not yet have that convenience for
|
|
// unmarshaling.
|
|
cfg, err := configutil.BytesToInternalConfig(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kdm.Configuration = cfg
|
|
kdm.bootstrap = true
|
|
}
|
|
if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
|
|
cfg, err := kubeadmutil.UnmarshalFromYamlForCodecs(b, kubeadmapi.SchemeGroupVersion, kubeadmscheme.Codecs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kdm.Configuration = cfg
|
|
kdm.bootstrap = false
|
|
joinConfiguration, ok := cfg.(*kubeadm.JoinConfiguration)
|
|
if !ok {
|
|
return errors.New("expected JoinConfiguration")
|
|
}
|
|
if joinConfiguration.ControlPlane == nil {
|
|
kdm.controlPlane = false
|
|
} else {
|
|
kdm.controlPlane = true
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Trustd describes the configuration of the Root of Trust (RoT) service. The
|
|
// username and password are used by master nodes, and worker nodes. The master
|
|
// nodes use them to authenticate clients, while the workers use them to
|
|
// authenticate as a client. The endpoints should only be specified in the
|
|
// worker user data, and should include all master nodes participating as a RoT.
|
|
type Trustd struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
Endpoints []string `yaml:"endpoints,omitempty"`
|
|
}
|
|
|
|
// OSD describes the configuration of the osd service.
|
|
type OSD struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
}
|
|
|
|
// Proxyd describes the configuration of the proxyd service.
|
|
type Proxyd struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
}
|
|
|
|
// Blockd describes the configuration of the blockd service.
|
|
type Blockd struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
}
|
|
|
|
// CRT describes the configuration of the container runtime service.
|
|
type CRT struct {
|
|
CommonServiceOptions `yaml:",inline"`
|
|
}
|
|
|
|
// CommonServiceOptions represents the set of options common to all services.
|
|
type CommonServiceOptions struct {
|
|
Image string `yaml:"image,omitempty"`
|
|
Env Env `yaml:"env,omitempty"`
|
|
}
|
|
|
|
// WriteFiles writes the requested files to disk.
|
|
func (data *UserData) WriteFiles() (err error) {
|
|
for _, f := range data.Files {
|
|
p := path.Join("/var", f.Path)
|
|
if err = os.MkdirAll(path.Dir(p), os.ModeDir); err != nil {
|
|
return
|
|
}
|
|
if err = ioutil.WriteFile(p, []byte(f.Contents), f.Permissions); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Download initializes a UserData struct from a remote URL.
|
|
func Download(url string) (data UserData, err error) {
|
|
maxRetries := 10
|
|
maxWait := float64(64)
|
|
|
|
for attempt := 0; attempt < maxRetries; attempt++ {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return data, err
|
|
}
|
|
// nolint: errcheck
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
log.Printf("Received %d\n", resp.StatusCode)
|
|
snooze := math.Pow(2, float64(attempt))
|
|
if snooze > maxWait {
|
|
snooze = maxWait
|
|
}
|
|
time.Sleep(time.Duration(snooze) * time.Second)
|
|
continue
|
|
}
|
|
|
|
dataBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return data, err
|
|
}
|
|
|
|
if err != nil {
|
|
return data, fmt.Errorf("read user data: %s", err.Error())
|
|
}
|
|
|
|
if err := yaml.Unmarshal(dataBytes, &data); err != nil {
|
|
return data, fmt.Errorf("unmarshal user data: %s", err.Error())
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
return data, fmt.Errorf("failed to download userdata from: %s", url)
|
|
}
|
|
|
|
// Open is a convenience function that reads the user data from disk, and
|
|
// unmarshals it.
|
|
func Open(p string) (data *UserData, err error) {
|
|
fileBytes, err := ioutil.ReadFile(p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read user data: %v", err)
|
|
}
|
|
|
|
data = &UserData{}
|
|
if err = yaml.Unmarshal(fileBytes, data); err != nil {
|
|
return nil, fmt.Errorf("unmarshal user data: %v", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// IsBootstrap indicates if the current kubeadm configuration is a master init
|
|
// configuration.
|
|
func (data *UserData) IsBootstrap() bool {
|
|
return data.Services.Kubeadm.bootstrap
|
|
}
|
|
|
|
// IsControlPlane indicates if the current kubeadm configuration is a worker
|
|
// acting as a master.
|
|
func (data *UserData) IsControlPlane() bool {
|
|
return data.Services.Kubeadm.controlPlane
|
|
}
|
|
|
|
// IsMaster indicates if the current kubeadm configuration is a master
|
|
// configuration.
|
|
func (data *UserData) IsMaster() bool {
|
|
return data.Services.Kubeadm.bootstrap || data.Services.Kubeadm.controlPlane
|
|
}
|
|
|
|
// IsWorker indicates if the current kubeadm configuration is a worker
|
|
// configuration.
|
|
func (data *UserData) IsWorker() bool {
|
|
return !data.IsMaster()
|
|
}
|