diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index cd52b95..5e1e724 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -8,10 +8,11 @@ import ( "strconv" "strings" - config "github.com/c3os-io/c3os/pkg/config" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" edgeVPNClient "github.com/mudler/edgevpn/api/client" "github.com/mudler/edgevpn/api/client/service" "github.com/mudler/edgevpn/pkg/node" + "github.com/urfave/cli" "gopkg.in/yaml.v2" ) @@ -110,7 +111,7 @@ Prints a vanilla YAML configuration on screen which can be used to bootstrap a c l = i } } - cc := &config.Config{C3OS: &config.C3OS{NetworkToken: node.GenerateNewConnectionData(l).Base64()}} + cc := &providerConfig.Config{C3OS: &providerConfig.C3OS{NetworkToken: node.GenerateNewConnectionData(l).Base64()}} y, _ := yaml.Marshal(cc) fmt.Println(string(y)) return nil diff --git a/internal/provider/bootstrap.go b/internal/provider/bootstrap.go index 406832e..5b50c1b 100644 --- a/internal/provider/bootstrap.go +++ b/internal/provider/bootstrap.go @@ -14,9 +14,9 @@ import ( "github.com/c3os-io/c3os/internal/machine" "github.com/c3os-io/c3os/internal/machine/openrc" "github.com/c3os-io/c3os/internal/machine/systemd" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" "github.com/c3os-io/c3os/internal/role" "github.com/c3os-io/c3os/internal/utils" - "github.com/c3os-io/c3os/internal/vpn" "github.com/c3os-io/c3os/pkg/bus" "github.com/c3os-io/c3os/pkg/config" @@ -28,21 +28,26 @@ func Bootstrap(e *pluggable.Event) pluggable.EventResponse { cfg := &bus.BootstrapPayload{} err := json.Unmarshal([]byte(e.Data), cfg) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed reading JSON input: %s input '%s'", err.Error(), e.Data)} + return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), e.Data) } c := &config.Config{} + providerConfig := &providerConfig.Config{} err = config.FromString(cfg.Config, c) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed reading JSON input: %s input '%s'", err.Error(), cfg.Config)} + return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), cfg.Config) } + err = config.FromString(cfg.Config, providerConfig) + if err != nil { + return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), cfg.Config) + } // TODO: this belong to a systemd service that is started instead - tokenNotDefined := (c.C3OS == nil || c.C3OS.NetworkToken == "") + tokenNotDefined := (providerConfig.C3OS != nil && providerConfig.C3OS.NetworkToken == "") - if c.C3OS == nil && !c.K3s.Enabled && !c.K3sAgent.Enabled { - return pluggable.EventResponse{State: "No config file supplied"} + if providerConfig.C3OS == nil && !providerConfig.K3s.Enabled && !providerConfig.K3sAgent.Enabled { + return pluggable.EventResponse{State: "no c3os or k3s configuration. nothing to do"} } utils.SH("elemental run-stage c3os-agent.bootstrap") @@ -50,13 +55,13 @@ func Bootstrap(e *pluggable.Event) pluggable.EventResponse { logLevel := "debug" - if c.C3OS != nil && c.C3OS.LogLevel != "" { - logLevel = c.C3OS.LogLevel + if providerConfig.C3OS != nil && providerConfig.C3OS.LogLevel != "" { + logLevel = providerConfig.C3OS.LogLevel } lvl, err := logging.LevelFromString(logLevel) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed setup VPN: %s", err.Error())} + return ErrorEvent("Failed setup VPN: %s", err.Error()) } // TODO: Fixup Logging to file @@ -66,7 +71,7 @@ func Bootstrap(e *pluggable.Event) pluggable.EventResponse { } logger, err := loggerCfg.Build() if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed setup VPN: %s", err.Error())} + return ErrorEvent("Failed setup VPN: %s", err.Error()) } logging.SetAllLoggers(lvl) @@ -76,26 +81,28 @@ func Bootstrap(e *pluggable.Event) pluggable.EventResponse { // Do onetimebootstrap if K3s or K3s-agent are enabled. // Those blocks are not required to be enabled in case of a c3os // full automated setup. Otherwise, they must be explicitly enabled. - if c.K3s.Enabled || c.K3sAgent.Enabled { - err := oneTimeBootstrap(log, c, func() error { return vpn.Setup(machine.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, c) }) + if providerConfig.K3s.Enabled || providerConfig.K3sAgent.Enabled { + err := oneTimeBootstrap(log, providerConfig, func() error { + return SetupVPN(machine.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, providerConfig) + }) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed setup: %s", err.Error())} + return ErrorEvent("Failed setup: %s", err.Error()) } return pluggable.EventResponse{} } else if tokenNotDefined { - return pluggable.EventResponse{Error: "No network token provided, exiting"} + return ErrorEvent("No network token provided, exiting") } logger.Info("Configuring VPN") - if err := vpn.Setup(machine.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, c); err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed setup VPN: %s", err.Error())} + if err := SetupVPN(machine.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, providerConfig); err != nil { + return ErrorEvent("Failed setup VPN: %s", err.Error()) } networkID := "c3os" - if c.C3OS != nil && c.C3OS.NetworkID != "" { - networkID = c.C3OS.NetworkID + if providerConfig.C3OS != nil && providerConfig.C3OS.NetworkID != "" { + networkID = providerConfig.C3OS.NetworkID } cc := service.NewClient( @@ -107,36 +114,36 @@ func Bootstrap(e *pluggable.Event) pluggable.EventResponse { service.WithClient(cc), service.WithUUID(machine.UUID()), service.WithStateDir("/usr/local/.c3os/state"), - service.WithNetworkToken(c.C3OS.NetworkToken), + service.WithNetworkToken(providerConfig.C3OS.NetworkToken), service.WithPersistentRoles("auto"), service.WithRoles( service.RoleKey{ Role: "master", - RoleHandler: role.Master(c), + RoleHandler: role.Master(c, providerConfig), }, service.RoleKey{ Role: "worker", - RoleHandler: role.Worker(c), + RoleHandler: role.Worker(c, providerConfig), }, service.RoleKey{ Role: "auto", - RoleHandler: role.Auto(c), + RoleHandler: role.Auto(c, providerConfig), }, ), } // Optionally set up a specific node role if the user has defined so - if c.C3OS.Role != "" { - nodeOpts = append(nodeOpts, service.WithDefaultRoles(c.C3OS.Role)) + if providerConfig.C3OS.Role != "" { + nodeOpts = append(nodeOpts, service.WithDefaultRoles(providerConfig.C3OS.Role)) } k, err := service.NewNode(nodeOpts...) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed creating node: %s", err.Error())} + return ErrorEvent("Failed creating node: %s", err.Error()) } err = k.Start(context.Background()) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed start: %s", err.Error())} + return ErrorEvent("Failed start: %s", err.Error()) } return pluggable.EventResponse{ @@ -146,7 +153,7 @@ func Bootstrap(e *pluggable.Event) pluggable.EventResponse { } } -func oneTimeBootstrap(l logging.StandardLogger, c *config.Config, vpnSetupFN func() error) error { +func oneTimeBootstrap(l logging.StandardLogger, c *providerConfig.Config, vpnSetupFN func() error) error { if role.SentinelExist() { l.Info("Sentinel exists, nothing to do. exiting.") return nil @@ -154,7 +161,7 @@ func oneTimeBootstrap(l logging.StandardLogger, c *config.Config, vpnSetupFN fun l.Info("One time bootstrap starting") var svc machine.Service - k3sConfig := config.K3s{} + k3sConfig := providerConfig.K3s{} svcName := "k3s" svcRole := "server" diff --git a/internal/provider/bootstrap_test.go b/internal/provider/bootstrap_test.go index 5224130..9c70785 100644 --- a/internal/provider/bootstrap_test.go +++ b/internal/provider/bootstrap_test.go @@ -6,8 +6,8 @@ import ( "os" . "github.com/c3os-io/c3os/internal/provider" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" "github.com/c3os-io/c3os/pkg/bus" - "github.com/c3os-io/c3os/pkg/config" "github.com/mudler/go-pluggable" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -27,8 +27,8 @@ var _ = Describe("Bootstrap provider", func() { Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(f.Name()) - cfg := &config.Config{ - C3OS: &config.C3OS{ + cfg := &providerConfig.Config{ + C3OS: &providerConfig.C3OS{ NetworkToken: "foo", }, } diff --git a/internal/provider/challenge.go b/internal/provider/challenge.go index 77b47c2..b7b2639 100644 --- a/internal/provider/challenge.go +++ b/internal/provider/challenge.go @@ -2,10 +2,11 @@ package provider import ( "encoding/json" - "fmt" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" "github.com/c3os-io/c3os/pkg/bus" "github.com/c3os-io/c3os/pkg/config" + "github.com/mudler/go-nodepair" "github.com/mudler/go-pluggable" ) @@ -14,13 +15,13 @@ func Challenge(e *pluggable.Event) pluggable.EventResponse { p := &bus.EventPayload{} err := json.Unmarshal([]byte(e.Data), p) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed reading JSON input: %s input '%s'", err.Error(), e.Data)} + return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), e.Data) } - cfg := &config.Config{} + cfg := &providerConfig.Config{} err = config.FromString(p.Config, cfg) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed reading JSON input: %s input '%s'", err.Error(), p.Config)} + return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), p.Config) } tk := "" diff --git a/internal/provider/challenge_test.go b/internal/provider/challenge_test.go index 232e495..cbd0c65 100644 --- a/internal/provider/challenge_test.go +++ b/internal/provider/challenge_test.go @@ -5,12 +5,14 @@ import ( "io/ioutil" "os" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" + . "github.com/c3os-io/c3os/internal/provider" "github.com/c3os-io/c3os/pkg/bus" - "github.com/c3os-io/c3os/pkg/config" "github.com/mudler/go-pluggable" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "gopkg.in/yaml.v2" ) var _ = Describe("Challenge provider", func() { @@ -26,12 +28,15 @@ var _ = Describe("Challenge provider", func() { Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(f.Name()) - cfg := &config.Config{ - C3OS: &config.C3OS{ + cfg := &providerConfig.Config{ + C3OS: &providerConfig.C3OS{ NetworkToken: "foo", }, } - c := &bus.EventPayload{Config: cfg.String()} + d, err := yaml.Marshal(cfg) + Expect(err).ToNot(HaveOccurred()) + + c := &bus.EventPayload{Config: string(d)} dat, err := json.Marshal(c) Expect(err).ToNot(HaveOccurred()) @@ -46,12 +51,14 @@ var _ = Describe("Challenge provider", func() { Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(f.Name()) - cfg := &config.Config{ - C3OS: &config.C3OS{ + cfg := &providerConfig.Config{ + C3OS: &providerConfig.C3OS{ NetworkToken: "", }, } - c := &bus.EventPayload{Config: cfg.String()} + d, err := yaml.Marshal(cfg) + Expect(err).ToNot(HaveOccurred()) + c := &bus.EventPayload{Config: string(d)} dat, err := json.Marshal(c) Expect(err).ToNot(HaveOccurred()) diff --git a/internal/provider/common.go b/internal/provider/common.go new file mode 100644 index 0000000..0969c84 --- /dev/null +++ b/internal/provider/common.go @@ -0,0 +1,11 @@ +package provider + +import ( + "fmt" + + "github.com/mudler/go-pluggable" +) + +func ErrorEvent(format string, a ...interface{}) pluggable.EventResponse { + return pluggable.EventResponse{Error: fmt.Sprintf(format, a...)} +} diff --git a/internal/provider/config/config.go b/internal/provider/config/config.go new file mode 100644 index 0000000..2ce67b2 --- /dev/null +++ b/internal/provider/config/config.go @@ -0,0 +1,24 @@ +package config + +type C3OS struct { + NetworkToken string `yaml:"network_token,omitempty"` + NetworkID string `yaml:"network_id,omitempty"` + Role string `yaml:"role,omitempty"` + DNS bool `yaml:"dns,omitempty"` + LogLevel string `yaml:"loglevel,omitempty"` +} + +type Config struct { + C3OS *C3OS `yaml:"c3os,omitempty"` + K3sAgent K3s `yaml:"k3s-agent,omitempty"` + K3s K3s `yaml:"k3s,omitempty"` + VPN map[string]string `yaml:"vpn,omitempty"` +} + +type K3s struct { + Env map[string]string `yaml:"env,omitempty"` + ReplaceEnv bool `yaml:"replace_env,omitempty"` + ReplaceArgs bool `yaml:"replace_args,omitempty"` + Args []string `yaml:"args,omitempty"` + Enabled bool `yaml:"enabled,omitempty"` +} diff --git a/internal/provider/install.go b/internal/provider/install.go index bcf327e..ac25fb3 100644 --- a/internal/provider/install.go +++ b/internal/provider/install.go @@ -3,7 +3,6 @@ package provider import ( "context" "encoding/json" - "fmt" "github.com/c3os-io/c3os/pkg/bus" "github.com/mudler/go-nodepair" @@ -14,18 +13,18 @@ func Install(e *pluggable.Event) pluggable.EventResponse { cfg := &bus.InstallPayload{} err := json.Unmarshal([]byte(e.Data), cfg) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed reading JSON input: %s", err.Error())} + return ErrorEvent("Failed reading JSON input: %s", err.Error()) } r := map[string]string{} ctx := context.Background() if err := nodepair.Receive(ctx, &r, nodepair.WithToken(cfg.Token)); err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed reading JSON input: %s", err.Error())} + return ErrorEvent("Failed reading JSON input: %s", err.Error()) } payload, err := json.Marshal(r) if err != nil { - return pluggable.EventResponse{Error: fmt.Sprintf("Failed marshalling JSON input: %s", err.Error())} + return ErrorEvent("Failed marshalling JSON input: %s", err.Error()) } return pluggable.EventResponse{ diff --git a/internal/vpn/setup.go b/internal/provider/vpn.go similarity index 92% rename from internal/vpn/setup.go rename to internal/provider/vpn.go index 16bb1ea..d12fe78 100644 --- a/internal/vpn/setup.go +++ b/internal/provider/vpn.go @@ -1,4 +1,4 @@ -package vpn +package provider import ( "fmt" @@ -8,12 +8,14 @@ import ( "github.com/c3os-io/c3os/internal/machine" "github.com/c3os-io/c3os/internal/machine/systemd" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" "github.com/c3os-io/c3os/internal/utils" "github.com/c3os-io/c3os/pkg/config" + yip "github.com/mudler/yip/pkg/schema" ) -func Setup(instance, apiAddress, rootDir string, start bool, c *config.Config) error { +func SetupVPN(instance, apiAddress, rootDir string, start bool, c *providerConfig.Config) error { if c.C3OS == nil || c.C3OS.NetworkToken == "" { return fmt.Errorf("no network token defined") diff --git a/internal/role/auto.go b/internal/role/auto.go index 48dc41b..34e08e4 100644 --- a/internal/role/auto.go +++ b/internal/role/auto.go @@ -2,6 +2,8 @@ package role import ( "github.com/c3os-io/c3os/pkg/config" + + providerConfig "github.com/c3os-io/c3os/internal/provider/config" utils "github.com/mudler/edgevpn/pkg/utils" service "github.com/mudler/edgevpn/api/client/service" @@ -15,7 +17,7 @@ func contains(slice []string, elem string) bool { } return false } -func Auto(cc *config.Config) Role { +func Auto(cc *config.Config, pconfig *providerConfig.Config) Role { return func(c *service.RoleConfig) error { advertizing, _ := c.Client.AdvertizingNodes() actives, _ := c.Client.ActiveNodes() @@ -52,6 +54,6 @@ func Auto(cc *config.Config) Role { return nil } - return scheduleRoles(nodes, c, cc) + return scheduleRoles(nodes, c, cc, pconfig) } } diff --git a/internal/role/master.go b/internal/role/master.go index cf30a36..8e69e92 100644 --- a/internal/role/master.go +++ b/internal/role/master.go @@ -9,9 +9,11 @@ import ( "time" "github.com/c3os-io/c3os/internal/machine" - "github.com/c3os-io/c3os/internal/utils" "github.com/c3os-io/c3os/pkg/config" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" + "github.com/c3os-io/c3os/internal/utils" + service "github.com/mudler/edgevpn/api/client/service" ) @@ -53,7 +55,7 @@ func propagateMasterData(ip string, c *service.RoleConfig) error { return nil } -func Master(cc *config.Config) Role { +func Master(cc *config.Config, pconfig *providerConfig.Config) Role { return func(c *service.RoleConfig) error { ip := utils.GetInterfaceIP("edgevpn0") @@ -61,10 +63,10 @@ func Master(cc *config.Config) Role { return errors.New("node doesn't have an ip yet") } - if cc.C3OS.Role != "" { + if pconfig.C3OS.Role != "" { // propagate role if we were forced by configuration // This unblocks eventual auto instances to try to assign roles - c.Client.Set("role", c.UUID, cc.C3OS.Role) + c.Client.Set("role", c.UUID, pconfig.C3OS.Role) } if SentinelExist() { @@ -80,9 +82,9 @@ func Master(cc *config.Config) Role { return err } - k3sConfig := config.K3s{} - if cc.K3s.Enabled { - k3sConfig = cc.K3s + k3sConfig := providerConfig.K3s{} + if pconfig.K3s.Enabled { + k3sConfig = pconfig.K3s } env := map[string]string{} diff --git a/internal/role/schedule.go b/internal/role/schedule.go index 80e6377..bbd639e 100644 --- a/internal/role/schedule.go +++ b/internal/role/schedule.go @@ -5,12 +5,14 @@ import ( "time" "github.com/c3os-io/c3os/pkg/config" + + providerConfig "github.com/c3os-io/c3os/internal/provider/config" service "github.com/mudler/edgevpn/api/client/service" ) // scheduleRoles assigns roles to nodes. Meant to be called only by leaders // TODO: HA-Auto -func scheduleRoles(nodes []string, c *service.RoleConfig, cc *config.Config) error { +func scheduleRoles(nodes []string, c *service.RoleConfig, cc *config.Config, pconfig *providerConfig.Config) error { rand.Seed(time.Now().Unix()) // Assign roles to nodes @@ -37,7 +39,7 @@ func scheduleRoles(nodes []string, c *service.RoleConfig, cc *config.Config) err toSelect := unassignedNodes // Avoid to schedule to ourselves if we have a static role - if cc.C3OS.Role != "" { + if pconfig.C3OS.Role != "" { toSelect = []string{} for _, u := range unassignedNodes { if u != c.UUID { diff --git a/internal/role/worker.go b/internal/role/worker.go index 929cd05..a10c643 100644 --- a/internal/role/worker.go +++ b/internal/role/worker.go @@ -9,16 +9,17 @@ import ( "github.com/c3os-io/c3os/internal/utils" "github.com/c3os-io/c3os/pkg/config" + providerConfig "github.com/c3os-io/c3os/internal/provider/config" service "github.com/mudler/edgevpn/api/client/service" ) -func Worker(cc *config.Config) Role { +func Worker(cc *config.Config, pconfig *providerConfig.Config) Role { return func(c *service.RoleConfig) error { - if cc.C3OS.Role != "" { + if pconfig.C3OS.Role != "" { // propagate role if we were forced by configuration // This unblocks eventual auto instances to try to assign roles - c.Client.Set("role", c.UUID, cc.C3OS.Role) + c.Client.Set("role", c.UUID, pconfig.C3OS.Role) } if SentinelExist() { @@ -52,9 +53,9 @@ func Worker(cc *config.Config) Role { return err } - k3sConfig := config.K3s{} - if cc.K3sAgent.Enabled { - k3sConfig = cc.K3sAgent + k3sConfig := providerConfig.K3s{} + if pconfig.K3sAgent.Enabled { + k3sConfig = pconfig.K3sAgent } env := map[string]string{