2022-07-04 20:39:34 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2022-07-07 16:57:38 +00:00
|
|
|
"net/http"
|
2022-07-04 20:39:34 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2022-07-13 22:23:47 +00:00
|
|
|
"strings"
|
2022-07-16 20:47:55 +00:00
|
|
|
"unicode"
|
2022-07-04 20:39:34 +00:00
|
|
|
|
2022-07-07 16:57:38 +00:00
|
|
|
retry "github.com/avast/retry-go"
|
2022-09-17 16:43:51 +00:00
|
|
|
"github.com/kairos-io/kairos/pkg/machine"
|
|
|
|
"github.com/kairos-io/kairos/sdk/bundles"
|
2022-07-04 20:39:34 +00:00
|
|
|
yip "github.com/mudler/yip/pkg/schema"
|
|
|
|
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
)
|
|
|
|
|
2022-07-16 20:47:55 +00:00
|
|
|
type Install struct {
|
2022-09-08 13:39:26 +00:00
|
|
|
Auto bool `yaml:"auto,omitempty"`
|
|
|
|
Reboot bool `yaml:"reboot,omitempty"`
|
|
|
|
Device string `yaml:"device,omitempty"`
|
|
|
|
Poweroff bool `yaml:"poweroff,omitempty"`
|
|
|
|
GrubOptions map[string]string `yaml:"grub_options,omitempty"`
|
2022-10-02 22:16:01 +00:00
|
|
|
Bundles Bundles `yaml:"bundles,omitempty"`
|
2022-10-07 11:36:32 +00:00
|
|
|
Encrypt []string `yaml:"encrypted_partitions,omitempty"`
|
2022-07-04 20:39:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Config struct {
|
2022-07-16 20:47:55 +00:00
|
|
|
Install *Install `yaml:"install,omitempty"`
|
2022-07-07 16:57:38 +00:00
|
|
|
//cloudFileContent string
|
2022-07-13 22:23:47 +00:00
|
|
|
originalData map[string]interface{}
|
|
|
|
location string
|
2022-07-16 20:47:55 +00:00
|
|
|
header string
|
2022-07-13 22:23:47 +00:00
|
|
|
ConfigURL string `yaml:"config_url,omitempty"`
|
|
|
|
Options map[string]string `yaml:"options,omitempty"`
|
2022-10-02 22:16:01 +00:00
|
|
|
FailOnBundleErrors bool `yaml:"fail_on_bundles_errors,omitempty"`
|
2022-07-13 22:23:47 +00:00
|
|
|
Bundles Bundles `yaml:"bundles,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Bundles []Bundle
|
|
|
|
|
|
|
|
type Bundle struct {
|
|
|
|
Repository string `yaml:"repository,omitempty"`
|
|
|
|
Rootfs string `yaml:"rootfs_path,omitempty"`
|
|
|
|
DB string `yaml:"db_path,omitempty"`
|
|
|
|
|
|
|
|
Targets []string `yaml:"targets,omitempty"`
|
|
|
|
}
|
|
|
|
|
2022-07-16 20:47:55 +00:00
|
|
|
func HasHeader(userdata, head string) (bool, string) {
|
|
|
|
header := strings.SplitN(userdata, "\n", 2)[0]
|
|
|
|
|
|
|
|
// Trim trailing whitespaces
|
|
|
|
header = strings.TrimRightFunc(header, unicode.IsSpace)
|
|
|
|
|
|
|
|
if head != "" {
|
|
|
|
return head == header, header
|
|
|
|
}
|
2022-09-17 16:43:51 +00:00
|
|
|
return (header == "#cloud-config") || (header == "#kairos-config") || (header == "#node-config"), header
|
2022-07-16 20:47:55 +00:00
|
|
|
}
|
|
|
|
|
2022-08-10 16:56:07 +00:00
|
|
|
func (b Bundles) Options() (res [][]bundles.BundleOption) {
|
2022-07-13 22:23:47 +00:00
|
|
|
for _, bundle := range b {
|
|
|
|
for _, t := range bundle.Targets {
|
2022-08-10 16:56:07 +00:00
|
|
|
opts := []bundles.BundleOption{bundles.WithRepository(bundle.Repository), bundles.WithTarget(t)}
|
2022-07-13 22:23:47 +00:00
|
|
|
if bundle.Rootfs != "" {
|
2022-08-10 16:56:07 +00:00
|
|
|
opts = append(opts, bundles.WithRootFS(bundle.Rootfs))
|
2022-07-13 22:23:47 +00:00
|
|
|
}
|
|
|
|
if bundle.DB != "" {
|
2022-08-10 16:56:07 +00:00
|
|
|
opts = append(opts, bundles.WithDBPath(bundle.DB))
|
2022-07-13 22:23:47 +00:00
|
|
|
}
|
|
|
|
res = append(res, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
2022-07-07 16:57:38 +00:00
|
|
|
}
|
|
|
|
|
2022-07-16 20:47:55 +00:00
|
|
|
func (c Config) Unmarshal(o interface{}) error {
|
|
|
|
return yaml.Unmarshal([]byte(c.String()), o)
|
|
|
|
}
|
|
|
|
|
2022-08-10 16:56:07 +00:00
|
|
|
func (c Config) Location() string {
|
|
|
|
return c.location
|
|
|
|
}
|
|
|
|
|
2022-07-07 16:57:38 +00:00
|
|
|
func (c Config) Data() map[string]interface{} {
|
|
|
|
return c.originalData
|
2022-07-04 20:39:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c Config) String() string {
|
2022-07-07 16:57:38 +00:00
|
|
|
if len(c.originalData) == 0 {
|
2022-07-04 20:39:34 +00:00
|
|
|
dat, err := yaml.Marshal(c)
|
|
|
|
if err == nil {
|
|
|
|
return string(dat)
|
|
|
|
}
|
|
|
|
}
|
2022-07-07 16:57:38 +00:00
|
|
|
|
|
|
|
dat, _ := yaml.Marshal(c.originalData)
|
2022-07-16 20:47:55 +00:00
|
|
|
if c.header != "" {
|
|
|
|
return AddHeader(c.header, string(dat))
|
|
|
|
}
|
2022-07-07 16:57:38 +00:00
|
|
|
return string(dat)
|
2022-07-04 20:39:34 +00:00
|
|
|
}
|
|
|
|
|
2022-07-13 22:23:47 +00:00
|
|
|
func (c Config) IsValid() bool {
|
2022-07-16 20:47:55 +00:00
|
|
|
return c.Install != nil ||
|
2022-07-13 22:23:47 +00:00
|
|
|
c.ConfigURL != "" ||
|
2022-07-16 20:47:55 +00:00
|
|
|
len(c.Bundles) != 0
|
2022-07-13 22:23:47 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 16:57:38 +00:00
|
|
|
func Scan(opts ...Option) (c *Config, err error) {
|
|
|
|
|
|
|
|
o := &Options{}
|
|
|
|
|
|
|
|
if err := o.Apply(opts...); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := o.ScanDir
|
|
|
|
|
2022-07-04 20:39:34 +00:00
|
|
|
c = &Config{}
|
|
|
|
files := []string{}
|
|
|
|
for _, d := range dir {
|
|
|
|
if f, err := listFiles(d); err == nil {
|
|
|
|
files = append(files, f...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 22:23:47 +00:00
|
|
|
configFound := false
|
|
|
|
lastYamlFileFound := ""
|
2022-07-25 22:26:10 +00:00
|
|
|
|
|
|
|
// Scanning happens as best-effort, therefore unmarshalling skips errors here.
|
2022-07-04 20:39:34 +00:00
|
|
|
for _, f := range files {
|
|
|
|
if fileSize(f) > 1.0 {
|
|
|
|
//fmt.Println("warning: Skipping file ", f, "as exceeds 1 MB in size")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(f)
|
|
|
|
if err == nil {
|
2022-07-25 22:26:10 +00:00
|
|
|
// best effort. skip lint checks
|
|
|
|
yaml.Unmarshal(b, c) //nolint:errcheck
|
2022-07-16 20:47:55 +00:00
|
|
|
if exists, header := HasHeader(string(b), ""); c.IsValid() || exists {
|
2022-07-04 20:39:34 +00:00
|
|
|
c.location = f
|
2022-07-25 22:26:10 +00:00
|
|
|
yaml.Unmarshal(b, &c.originalData) //nolint:errcheck
|
2022-07-13 22:23:47 +00:00
|
|
|
configFound = true
|
2022-07-16 20:47:55 +00:00
|
|
|
if exists {
|
|
|
|
c.header = header
|
|
|
|
}
|
2022-07-04 20:39:34 +00:00
|
|
|
break
|
|
|
|
}
|
2022-07-13 22:23:47 +00:00
|
|
|
|
|
|
|
// record back the only yaml file found (if any)
|
|
|
|
if strings.HasSuffix(strings.ToLower(f), "yaml") || strings.HasSuffix(strings.ToLower(f), "yml") {
|
|
|
|
lastYamlFileFound = f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// use last recorded if no config is found valid
|
|
|
|
if !configFound && lastYamlFileFound != "" {
|
|
|
|
b, err := ioutil.ReadFile(lastYamlFileFound)
|
|
|
|
if err == nil {
|
2022-07-25 22:26:10 +00:00
|
|
|
yaml.Unmarshal(b, c) //nolint:errcheck
|
2022-07-13 22:23:47 +00:00
|
|
|
c.location = lastYamlFileFound
|
2022-07-25 22:26:10 +00:00
|
|
|
yaml.Unmarshal(b, &c.originalData) //nolint:errcheck
|
2022-07-04 20:39:34 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-07 16:57:38 +00:00
|
|
|
|
|
|
|
if o.MergeBootCMDLine {
|
|
|
|
d, err := machine.DotToYAML(o.BootCMDLineFile)
|
|
|
|
if err == nil { // best-effort
|
2022-07-25 22:26:10 +00:00
|
|
|
yaml.Unmarshal(d, c) //nolint:errcheck
|
2022-07-07 16:57:38 +00:00
|
|
|
// Merge back to originalData only config which are part of the config structure
|
|
|
|
// This avoid garbage as unrelated bootargs to be merged in.
|
|
|
|
dat, err := yaml.Marshal(c)
|
|
|
|
if err == nil {
|
2022-07-25 22:26:10 +00:00
|
|
|
yaml.Unmarshal(dat, &c.originalData) //nolint:errcheck
|
2022-07-07 16:57:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.ConfigURL != "" {
|
|
|
|
var body []byte
|
|
|
|
|
|
|
|
err := retry.Do(
|
|
|
|
func() error {
|
|
|
|
resp, err := http.Get(c.ConfigURL)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err = ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return c, fmt.Errorf("could not merge configs: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-07-25 22:26:10 +00:00
|
|
|
yaml.Unmarshal(body, c) //nolint:errcheck
|
|
|
|
yaml.Unmarshal(body, &c.originalData) //nolint:errcheck
|
2022-07-07 16:57:38 +00:00
|
|
|
}
|
|
|
|
|
2022-07-04 20:39:34 +00:00
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func fileSize(f string) float64 {
|
|
|
|
file, err := os.Open(f)
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
stat, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2022-07-25 22:26:10 +00:00
|
|
|
bytes := stat.Size()
|
|
|
|
kilobytes := (bytes / 1024)
|
|
|
|
megabytes := (float64)(kilobytes / 1024) // cast to type float64
|
2022-07-04 20:39:34 +00:00
|
|
|
|
|
|
|
return megabytes
|
|
|
|
}
|
2022-07-07 16:57:38 +00:00
|
|
|
|
2022-07-04 20:39:34 +00:00
|
|
|
func listFiles(dir string) ([]string, error) {
|
|
|
|
content := []string{}
|
|
|
|
|
|
|
|
err := filepath.Walk(dir,
|
|
|
|
func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
content = append(content, path)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return content, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type Stage string
|
|
|
|
|
|
|
|
const (
|
|
|
|
NetworkStage Stage = "network"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (n Stage) String() string {
|
|
|
|
return string(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func SaveCloudConfig(name Stage, yc yip.YipConfig) error {
|
|
|
|
dnsYAML, err := yaml.Marshal(yc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return ioutil.WriteFile(filepath.Join("usr", "local", "cloud-config", fmt.Sprintf("100_%s.yaml", name)), dnsYAML, 0700)
|
|
|
|
}
|
|
|
|
|
|
|
|
func FromString(s string, o interface{}) error {
|
|
|
|
return yaml.Unmarshal([]byte(s), o)
|
|
|
|
}
|
2022-07-16 20:47:55 +00:00
|
|
|
|
|
|
|
func MergeYAML(objs ...interface{}) ([]byte, error) {
|
|
|
|
content := [][]byte{}
|
|
|
|
for _, o := range objs {
|
|
|
|
dat, err := yaml.Marshal(o)
|
|
|
|
if err != nil {
|
|
|
|
return []byte{}, err
|
|
|
|
}
|
|
|
|
content = append(content, dat)
|
|
|
|
}
|
|
|
|
|
|
|
|
finalData := make(map[string]interface{})
|
|
|
|
|
|
|
|
for _, c := range content {
|
|
|
|
if err := yaml.Unmarshal(c, &finalData); err != nil {
|
|
|
|
return []byte{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return yaml.Marshal(finalData)
|
|
|
|
}
|
|
|
|
|
|
|
|
func AddHeader(header, data string) string {
|
|
|
|
return fmt.Sprintf("%s\n%s", header, data)
|
|
|
|
}
|