1
0
mirror of https://github.com/rancher/os.git synced 2025-09-25 12:47:20 +00:00

Add ipxe support

This commit is contained in:
Darren Shepherd
2021-10-13 16:00:29 -07:00
parent b4cc0014b2
commit 0e46d19194
16 changed files with 287 additions and 126 deletions

View File

@@ -1,6 +1,6 @@
package config
type Rancher struct {
type RancherOS struct {
Install Install `json:"install,omitempty"`
}
@@ -15,31 +15,22 @@ type Install struct {
NoFormat bool `json:"noFormat,omitempty"`
Debug bool `json:"debug,omitempty"`
TTY string `json:"tty,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
Token string `json:"token,omitempty"`
Password string `json:"password,omitempty"`
}
type Config struct {
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`
Rancher Rancher `json:"rancher,omitempty"`
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`
RancherOS RancherOS `json:"rancheros,omitempty"`
}
type YipConfig struct {
Stages map[string][]Stage `json:"stages,omitempty"`
Rancherd Rancherd `json:"rancherd,omitempty"`
Stages map[string][]Stage `json:"stages,omitempty"`
}
type Stage struct {
Users map[string]User `json:"users,omitempty"`
}
type Rancherd struct {
Server string `json:"server,omitempty"`
Role string `json:"role,omitempty"`
Token string `json:"token,omitempty"`
}
type User struct {
Name string `json:"name,omitempty"`
PasswordHash string `json:"passwd,omitempty"`

View File

@@ -3,6 +3,7 @@ package config
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"
@@ -10,18 +11,20 @@ import (
values "github.com/rancher/wrangler/pkg/data"
"github.com/rancher/wrangler/pkg/data/convert"
schemas2 "github.com/rancher/wrangler/pkg/schemas"
"sigs.k8s.io/yaml"
)
var (
defaultMappers = schemas2.Mappers{
NewToMap(),
NewToSlice(),
NewToBool(),
&FuzzyNames{},
}
schemas = schemas2.EmptySchemas().Init(func(s *schemas2.Schemas) *schemas2.Schemas {
s.DefaultMapper = func() schemas2.Mapper {
return schemas2.Mappers{
NewToMap(),
NewToSlice(),
NewToBool(),
&FuzzyNames{},
}
}
s.AddMapper("config", defaultMappers)
s.AddMapper("rancherOS", defaultMappers)
s.AddMapper("install", defaultMappers)
return s
}).MustImport(Config{})
schema = schemas.Schema("config")
@@ -40,7 +43,7 @@ func mapToEnv(prefix string, data map[string]interface{}) []string {
var result []string
for k, v := range data {
keyName := strings.ToUpper(prefix + convert.ToYAMLKey(k))
keyName = strings.ReplaceAll(keyName, "RANCHER_", "COS_")
keyName = strings.ReplaceAll(keyName, "RANCHEROS_", "COS_")
if data, ok := v.(map[string]interface{}); ok {
subResult := mapToEnv(keyName+"_", data)
result = append(result, subResult...)
@@ -51,14 +54,86 @@ func mapToEnv(prefix string, data map[string]interface{}) []string {
return result
}
func ReadConfig() (Config, error) {
result := Config{}
func readFile(path string) (result map[string]interface{}, _ error) {
result = map[string]interface{}{}
defer func() {
if v, ok := result["install"]; ok {
values.PutValue(result, v, "rancheros", "install")
}
}()
data, err := readCmdline()
if err != nil {
return result, err
switch {
case strings.HasPrefix(path, "http://"):
fallthrough
case strings.HasPrefix(path, "https://"):
resp, err := http.Get(path)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buffer, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}
return result, yaml.Unmarshal(buffer, &result)
case strings.HasPrefix(path, "tftp://"):
return tftpGet(path)
}
if err := schema.Mapper.ToInternal(data); err != nil {
f, err := ioutil.ReadFile(path)
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
data := map[string]interface{}{}
if err := yaml.Unmarshal(f, &data); err != nil {
return nil, err
}
return data, nil
}
type reader func() (map[string]interface{}, error)
func merge(readers ...reader) (map[string]interface{}, error) {
d := map[string]interface{}{}
for _, r := range readers {
newData, err := r()
if err != nil {
return nil, err
}
if err := schema.Mapper.ToInternal(newData); err != nil {
return nil, err
}
d = values.MergeMapsConcatSlice(d, newData)
}
return d, nil
}
func readConfigMap(cfg string) (map[string]interface{}, error) {
var (
err error
data map[string]interface{}
)
return merge(func() (map[string]interface{}, error) {
data, err = merge(readCmdline,
func() (map[string]interface{}, error) {
return readFile(cfg)
},
)
return data, err
}, func() (map[string]interface{}, error) {
return readFile(convert.ToString(values.GetValueN(data, "rancheros", "install", "configUrl")))
})
}
func ReadConfig(cfg string) (result Config, err error) {
data, err := readConfigMap(cfg)
if err != nil {
return result, err
}
@@ -72,7 +147,11 @@ func readCmdline() (map[string]interface{}, error) {
return nil, nil
}
bytes, err := ioutil.ReadFile("/proc/cmdline")
procCmdLine := os.Getenv("PROC_CMDLINE")
if procCmdLine == "" {
procCmdLine = "/proc/cmdline"
}
bytes, err := ioutil.ReadFile(procCmdLine)
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {

View File

@@ -30,7 +30,9 @@ func (f *FuzzyNames) addName(name, toName string) {
}
func (f *FuzzyNames) ModifySchema(schema *schemas2.Schema, schemas *schemas2.Schemas) error {
f.names = map[string]string{}
if f.names == nil {
f.names = map[string]string{}
}
for name := range schema.ResourceFields {
if strings.HasSuffix(name, "s") && len(name) > 1 {

41
pkg/config/tftpget.go Normal file
View File

@@ -0,0 +1,41 @@
package config
import (
"bytes"
"fmt"
"net"
"net/url"
"gopkg.in/pin/tftp.v2"
"sigs.k8s.io/yaml"
)
func tftpGet(tftpURL string) (map[string]interface{}, error) {
u, err := url.Parse(tftpURL)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
host = u.Host + ":69"
}
fmt.Printf("Downloading config from host %s, file %s\n", host, u.Path)
client, err := tftp.NewClient(host)
if err != nil {
return nil, err
}
writerTo, err := client.Receive(u.Path, "octet")
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
if _, err := writerTo.WriteTo(buf); err != nil {
return nil, err
}
result := map[string]interface{}{}
return result, yaml.Unmarshal(buf.Bytes(), &result)
}

View File

@@ -6,10 +6,10 @@ import (
)
func PrintInstall(cfg Config) ([]byte, error) {
if cfg.Rancher.Install.Password != "" {
cfg.Rancher.Install.Password = "******"
if cfg.RancherOS.Install.Password != "" {
cfg.RancherOS.Install.Password = "******"
}
data, err := convert.EncodeToMap(cfg.Rancher.Install)
data, err := convert.EncodeToMap(cfg.RancherOS.Install)
if err != nil {
return nil, err
}

View File

@@ -10,10 +10,6 @@ import (
)
func Ask(cfg *config.Config) error {
if cfg.Rancher.Install.Silent {
return nil
}
if err := AskInstallDevice(cfg); err != nil {
return err
}
@@ -22,7 +18,7 @@ func Ask(cfg *config.Config) error {
return err
}
if cfg.Rancher.Install.ConfigURL == "" {
if cfg.RancherOS.Install.ConfigURL == "" {
if err := AskGithub(cfg); err != nil {
return err
}
@@ -30,17 +26,13 @@ func Ask(cfg *config.Config) error {
if err := AskPassword(cfg); err != nil {
return err
}
if err := AskServerAgent(cfg); err != nil {
return err
}
}
return nil
}
func AskInstallDevice(cfg *config.Config) error {
if cfg.Rancher.Install.Device != "" {
if cfg.RancherOS.Install.Device != "" {
return nil
}
@@ -54,69 +46,22 @@ func AskInstallDevice(cfg *config.Config) error {
return err
}
cfg.Rancher.Install.Device = "/dev/" + fields[i]
cfg.RancherOS.Install.Device = "/dev/" + fields[i]
return nil
}
func AskToken(cfg *config.Config, server bool) error {
var (
token string
err error
)
if cfg.Rancher.Install.Token != "" {
return nil
}
msg := "Token or cluster secret"
if server {
msg += " (optional)"
}
if server {
token, err = questions.PromptOptional(msg+": ", "")
} else {
token, err = questions.Prompt(msg+": ", "")
}
cfg.Rancher.Install.Token = token
return err
}
func isServer(cfg *config.Config) (bool, error) {
opts := []string{"server", "agent"}
func isServer(cfg *config.Config) (bool, bool, error) {
opts := []string{"server", "agent", "none"}
i, err := questions.PromptFormattedOptions("Run as server or agent?", 0, opts...)
if err != nil {
return false, err
return false, false, err
}
return i == 0, nil
}
func AskServerAgent(cfg *config.Config) error {
if cfg.Rancher.Install.ServerURL != "" {
return nil
}
server, err := isServer(cfg)
if err != nil {
return err
}
if server {
return AskToken(cfg, true)
}
url, err := questions.Prompt("URL of server: ", "")
if err != nil {
return err
}
cfg.Rancher.Install.ServerURL = url
return AskToken(cfg, false)
return i == 0, i == 1, nil
}
func AskPassword(cfg *config.Config) error {
if cfg.Rancher.Install.Silent || cfg.Rancher.Install.Password != "" {
if cfg.RancherOS.Install.Silent || cfg.RancherOS.Install.Password != "" {
return nil
}
@@ -140,12 +85,12 @@ func AskPassword(cfg *config.Config) error {
}
}
cfg.Rancher.Install.Password = pass
cfg.RancherOS.Install.Password = pass
return nil
}
func AskGithub(cfg *config.Config) error {
if len(cfg.SSHAuthorizedKeys) > 0 || cfg.Rancher.Install.Password != "" {
if len(cfg.SSHAuthorizedKeys) > 0 || cfg.RancherOS.Install.Password != "" || cfg.RancherOS.Install.Silent {
return nil
}
@@ -167,7 +112,7 @@ func AskGithub(cfg *config.Config) error {
}
func AskConfigURL(cfg *config.Config) error {
if cfg.Rancher.Install.ConfigURL != "" {
if cfg.RancherOS.Install.ConfigURL != "" || cfg.RancherOS.Install.Silent {
return nil
}
@@ -185,6 +130,6 @@ func AskConfigURL(cfg *config.Config) error {
return err
}
cfg.Rancher.Install.ConfigURL = str
cfg.RancherOS.Install.ConfigURL = str
return nil
}

View File

@@ -1,6 +1,7 @@
package install
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
@@ -10,16 +11,16 @@ import (
"sigs.k8s.io/yaml"
)
func Run(automatic bool) error {
cfg, err := config.ReadConfig()
func Run(automatic bool, configFile string) error {
cfg, err := config.ReadConfig(configFile)
if err != nil {
return err
}
if automatic && !cfg.Rancher.Install.Automatic {
if automatic && !cfg.RancherOS.Install.Automatic {
return nil
} else if automatic {
cfg.Rancher.Install.Silent = true
cfg.RancherOS.Install.Silent = true
}
err = Ask(&cfg)
@@ -44,7 +45,7 @@ func runInstall(cfg config.Config, output string) error {
return err
}
if !cfg.Rancher.Install.Silent {
if !cfg.RancherOS.Install.Silent {
val, err := questions.PromptBool("\nConfiguration\n"+"-------------\n\n"+
string(installBytes)+
"\nYour disk will be formatted and installed with the above configuration.\nContinue?", false)
@@ -53,30 +54,20 @@ func runInstall(cfg config.Config, output string) error {
}
}
if cfg.Rancher.Install.ConfigURL == "" {
yip := config.YipConfig{
Rancherd: config.Rancherd{
Server: cfg.Rancher.Install.ServerURL,
Token: cfg.Rancher.Install.Token,
},
}
if cfg.Rancher.Install.ServerURL == "" {
yip.Rancherd.Role = "cluster-init"
} else {
yip.Rancherd.Role = "agent"
}
if cfg.Rancher.Install.Password != "" || len(cfg.SSHAuthorizedKeys) > 0 {
if cfg.RancherOS.Install.ConfigURL == "" && !cfg.RancherOS.Install.Silent {
yip := config.YipConfig{}
if cfg.RancherOS.Install.Password != "" || len(cfg.SSHAuthorizedKeys) > 0 {
yip.Stages = map[string][]config.Stage{
"network": {{
Users: map[string]config.User{
"root": {
Name: "root",
PasswordHash: cfg.Rancher.Install.Password,
PasswordHash: cfg.RancherOS.Install.Password,
SSHAuthorizedKeys: cfg.SSHAuthorizedKeys,
},
}},
}}
cfg.Rancher.Install.Password = ""
cfg.RancherOS.Install.Password = ""
}
data, err := yaml.Marshal(yip)
@@ -87,7 +78,7 @@ func runInstall(cfg config.Config, output string) error {
if err := ioutil.WriteFile(output+".yip", data, 0600); err != nil {
return err
}
cfg.Rancher.Install.ConfigURL = output + ".yip"
cfg.RancherOS.Install.ConfigURL = output + ".yip"
}
ev, err := config.ToEnv(cfg)
@@ -95,6 +86,8 @@ func runInstall(cfg config.Config, output string) error {
return err
}
printEnv(cfg)
cmd := exec.Command("cos-installer")
cmd.Env = append(os.Environ(), ev...)
cmd.Stdout = os.Stdout
@@ -102,3 +95,19 @@ func runInstall(cfg config.Config, output string) error {
cmd.Stderr = os.Stderr
return cmd.Run()
}
func printEnv(cfg config.Config) {
if cfg.RancherOS.Install.Password != "" {
cfg.RancherOS.Install.Password = "<removed>"
}
ev2, err := config.ToEnv(cfg)
if err != nil {
return
}
fmt.Println("Install environment:")
for _, ev := range ev2 {
fmt.Println(ev)
}
}