art: Drop provider from c3os code

Part of: https://github.com/c3os-io/c3os/issues/68
This commit is contained in:
mudler 2022-08-10 18:56:07 +02:00 committed by Itxaka
parent b19a93e984
commit 3c6484de14
39 changed files with 26 additions and 2098 deletions

View File

@ -9,8 +9,9 @@ import (
events "github.com/c3os-io/c3os/sdk/bus"
"github.com/c3os-io/c3os/internal/bus"
machine "github.com/c3os-io/c3os/internal/machine"
config "github.com/c3os-io/c3os/pkg/config"
machine "github.com/c3os-io/c3os/pkg/machine"
bundles "github.com/c3os-io/c3os/sdk/bundles"
"github.com/nxadm/tail"
)
@ -61,7 +62,7 @@ func Run(opts ...Option) error {
if !machine.SentinelExist("bundles") {
opts := c.Bundles.Options()
err := machine.RunBundles(opts...)
err := bundles.RunBundles(opts...)
if !c.IgnoreBundleErrors && err != nil {
return err
}

View File

@ -15,10 +15,12 @@ import (
config "github.com/c3os-io/c3os/pkg/config"
"github.com/c3os-io/c3os/internal/bus"
"github.com/c3os-io/c3os/internal/cmd"
"github.com/c3os-io/c3os/internal/utils"
sdkBus "github.com/c3os-io/c3os/sdk/bus"
machine "github.com/c3os-io/c3os/internal/machine"
"github.com/c3os-io/c3os/internal/cmd"
"github.com/c3os-io/c3os/pkg/utils"
machine "github.com/c3os-io/c3os/pkg/machine"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/mudler/go-pluggable"
"github.com/pterm/pterm"
@ -154,8 +156,8 @@ func Install(dir ...string) error {
r["cc"] = config.AddHeader(header, string(out))
pterm.Info.Println("Starting installation")
utils.SH("elemental run-stage c3os-install.pre") //nolint:errcheck
bus.RunHookScript("/usr/bin/c3os-agent.install.pre.hook") //nolint:errcheck
utils.SH("elemental run-stage c3os-install.pre") //nolint:errcheck
sdkBus.RunHookScript("/usr/bin/c3os-agent.install.pre.hook") //nolint:errcheck
if err := RunInstall(r); err != nil {
return err
@ -214,8 +216,8 @@ func RunInstall(options map[string]string) error {
fmt.Println(err)
os.Exit(1)
}
utils.SH("elemental run-stage c3os-install.after") //nolint:errcheck
bus.RunHookScript("/usr/bin/c3os-agent.install.after.hook") //nolint:errcheck
utils.SH("elemental run-stage c3os-install.after") //nolint:errcheck
sdkBus.RunHookScript("/usr/bin/c3os-agent.install.after.hook") //nolint:errcheck
if reboot || c.Install != nil && c.Install.Reboot {
utils.Reboot()

View File

@ -6,8 +6,8 @@ import (
"github.com/c3os-io/c3os/internal/cmd"
providerConfig "github.com/c3os-io/c3os/internal/provider/config"
"github.com/c3os-io/c3os/internal/utils"
config "github.com/c3os-io/c3os/pkg/config"
"github.com/c3os-io/c3os/pkg/utils"
"github.com/erikgeiser/promptkit/textinput"
"github.com/jaypipes/ghw"
"github.com/mudler/edgevpn/pkg/node"
@ -94,15 +94,12 @@ func InteractiveInstall(spawnShell bool) error {
userPassword = "!"
}
sshUsername, err := prompt("Username to grant SSH access to (github/gitlab supported)", "github:someuser", canBeEmpty, true, false)
users, err := prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
if err != nil {
return err
}
sshPubkey, err := prompt("SSH pubkey", "github:username", canBeEmpty, true, false)
if err != nil {
return err
}
sshUsers := strings.Split(users, ",")
k3sAuto, err := prompt("Do you want to enable k3s automated setup? (requires multiple nodes)", "n", yesNo, true, false)
if err != nil {
@ -158,16 +155,10 @@ func InteractiveInstall(spawnShell bool) error {
if userName != "" {
user := schema.User{
Name: userName,
PasswordHash: userPassword,
Groups: []string{"admin"},
}
if sshUsername != "" {
user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, sshUsername)
}
if sshPubkey != "" {
user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, sshPubkey)
Name: userName,
PasswordHash: userPassword,
Groups: []string{"admin"},
SSHAuthorizedKeys: sshUsers,
}
usersToSet = map[string]schema.User{

View File

@ -8,11 +8,11 @@ import (
"time"
"github.com/c3os-io/c3os/internal/cmd"
"github.com/c3os-io/c3os/internal/utils"
config "github.com/c3os-io/c3os/pkg/config"
"github.com/c3os-io/c3os/pkg/utils"
"github.com/ipfs/go-log"
machine "github.com/c3os-io/c3os/internal/machine"
machine "github.com/c3os-io/c3os/pkg/machine"
"github.com/creack/pty"
"github.com/gliderlabs/ssh"
"github.com/mudler/edgevpn/pkg/logger"

View File

@ -8,8 +8,8 @@ import (
"time"
"github.com/c3os-io/c3os/internal/cmd"
"github.com/c3os-io/c3os/internal/machine"
"github.com/c3os-io/c3os/internal/utils"
"github.com/c3os-io/c3os/pkg/machine"
"github.com/c3os-io/c3os/pkg/utils"
"github.com/pterm/pterm"
)

View File

@ -1,40 +0,0 @@
package agent
import (
machine "github.com/c3os-io/c3os/internal/machine"
"github.com/c3os-io/c3os/internal/provider"
providerConfig "github.com/c3os-io/c3os/internal/provider/config"
config "github.com/c3os-io/c3os/pkg/config"
)
func RotateToken(configDir []string, newToken, apiAddress, rootDir string, restart bool) error {
if err := config.ReplaceToken(configDir, newToken); err != nil {
return err
}
c, err := config.Scan(config.Directories(configDir...))
if err != nil {
return err
}
providerCfg := &providerConfig.Config{}
err = c.Unmarshal(providerCfg)
if err != nil {
return err
}
err = provider.SetupVPN(machine.EdgeVPNDefaultInstance, apiAddress, rootDir, false, providerCfg)
if err != nil {
return err
}
if restart {
svc, err := machine.EdgeVPN(machine.EdgeVPNDefaultInstance, rootDir)
if err != nil {
return err
}
return svc.Restart()
}
return nil
}

View File

@ -8,7 +8,7 @@ import (
"os/exec"
"github.com/c3os-io/c3os/internal/github"
"github.com/c3os-io/c3os/internal/utils"
"github.com/c3os-io/c3os/pkg/utils"
)
func Upgrade(version, image string, force bool) error {

View File

@ -3,7 +3,6 @@ package bus
import (
"fmt"
"os"
"os/exec"
"github.com/c3os-io/c3os/sdk/bus"
@ -13,12 +12,7 @@ import (
// Manager is the bus instance manager, which subscribes plugins to events emitted.
var Manager = &Bus{
Manager: pluggable.NewManager(
[]pluggable.EventType{
bus.EventBootstrap,
bus.EventChallenge,
bus.EventBoot,
bus.EventInstall,
},
bus.AllEvents,
),
}
@ -57,15 +51,3 @@ func (b *Bus) Initialize() {
})
}
}
func RunHookScript(s string) error {
_, err := os.Stat(s)
if err != nil {
return nil
}
cmd := exec.Command(s)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
return cmd.Run()
}

View File

@ -6,7 +6,7 @@ import (
"os"
"github.com/c3os-io/c3os/internal/c3os"
"github.com/c3os-io/c3os/internal/utils"
"github.com/c3os-io/c3os/pkg/utils"
"github.com/pterm/pterm"
)

View File

@ -1,88 +0,0 @@
package machine
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"github.com/google/shlex"
"github.com/hashicorp/go-multierror"
"github.com/itchyny/gojq"
"gopkg.in/yaml.v2"
)
func DotToYAML(file string) ([]byte, error) {
if file == "" {
file = "/proc/cmdline"
}
dat, err := ioutil.ReadFile(file)
if err != nil {
return []byte{}, err
}
v := stringToMap(string(dat))
return dotToYAML(v)
}
func stringToMap(s string) map[string]interface{} {
v := map[string]interface{}{}
splitted, _ := shlex.Split(s)
for _, item := range splitted {
parts := strings.SplitN(item, "=", 2)
value := "true"
if len(parts) > 1 {
value = strings.Trim(parts[1], `"`)
}
key := strings.Trim(parts[0], `"`)
v[key] = value
}
return v
}
func jq(command string, data map[string]interface{}) (map[string]interface{}, error) {
query, err := gojq.Parse(command)
if err != nil {
return nil, err
}
code, err := gojq.Compile(query)
if err != nil {
return nil, err
}
iter := code.Run(data)
v, ok := iter.Next()
if !ok {
return nil, errors.New("failed getting rsult from gojq")
}
if err, ok := v.(error); ok {
return nil, err
}
if t, ok := v.(map[string]interface{}); ok {
return t, nil
}
return make(map[string]interface{}), nil
}
func dotToYAML(v map[string]interface{}) ([]byte, error) {
data := map[string]interface{}{}
var errs error
for k, value := range v {
newData, err := jq(fmt.Sprintf(".%s=\"%s\"", k, value), data)
if err != nil {
errs = multierror.Append(errs, err)
continue
}
data = newData
}
out, err := yaml.Marshal(&data)
if err != nil {
errs = multierror.Append(errs, err)
}
return out, errs
}

View File

@ -1,29 +0,0 @@
package machine_test
import (
"io/ioutil"
"os"
. "github.com/c3os-io/c3os/internal/machine"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("BootCMDLine", func() {
Context("parses data", func() {
It("returns cmdline if provided", func() {
f, err := ioutil.TempFile("", "test")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f.Name())
err = ioutil.WriteFile(f.Name(), []byte(`config_url="foo bar" baz.bar=""`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
b, err := DotToYAML(f.Name())
Expect(err).ToNot(HaveOccurred())
Expect(string(b)).To(Equal("baz:\n bar: \"\"\nconfig_url: foo bar\n"))
})
})
})

View File

@ -1,34 +0,0 @@
package machine_test
import (
"io/ioutil"
"os"
"path/filepath"
. "github.com/c3os-io/c3os/internal/machine"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Bundle", func() {
Context("install", func() {
PIt("installs packages from luet repos", func() {
dir, err := ioutil.TempDir("", "test")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(dir)
os.MkdirAll(filepath.Join(dir, "var", "tmp", "luet"), os.ModePerm)
err = RunBundles([]BundleOption{WithDBPath(dir), WithRootFS(dir), WithTarget("package://utils/edgevpn")})
Expect(err).ToNot(HaveOccurred())
Expect(filepath.Join(dir, "usr", "bin", "edgevpn")).To(BeARegularFile())
})
It("installs from container images", func() {
dir, err := ioutil.TempDir("", "test")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(dir)
err = RunBundles([]BundleOption{WithDBPath(dir), WithRootFS(dir), WithTarget("container://quay.io/mocaccino/extra:edgevpn-utils-0.15.0")})
Expect(err).ToNot(HaveOccurred())
Expect(filepath.Join(dir, "usr", "bin", "edgevpn")).To(BeARegularFile())
})
})
})

View File

@ -1,229 +0,0 @@
package machine
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/c3os-io/c3os/internal/utils"
"github.com/hashicorp/go-multierror"
)
type BundleConfig struct {
Target string
Repository string
DBPath string
RootPath string
}
// BundleOption defines a configuration option for a bundle.
type BundleOption func(bc *BundleConfig) error
// Apply applies bundle options to the config.
func (bc *BundleConfig) Apply(opts ...BundleOption) error {
for _, o := range opts {
if err := o(bc); err != nil {
return err
}
}
return nil
}
// WithDBPath sets the DB path for package installs.
// In case of luet packages will contain the db of the installed packages.
func WithDBPath(r string) BundleOption {
return func(bc *BundleConfig) error {
bc.DBPath = r
return nil
}
}
func WithRootFS(r string) BundleOption {
return func(bc *BundleConfig) error {
bc.RootPath = r
return nil
}
}
func WithRepository(r string) BundleOption {
return func(bc *BundleConfig) error {
bc.Repository = r
return nil
}
}
func WithTarget(p string) BundleOption {
return func(bc *BundleConfig) error {
bc.Target = p
return nil
}
}
func (bc *BundleConfig) extractRepo() (string, string, error) {
s := strings.Split(bc.Repository, "://")
if len(s) != 2 {
return "", "", fmt.Errorf("invalid repo schema")
}
return s[0], s[1], nil
}
func defaultConfig() *BundleConfig {
return &BundleConfig{
DBPath: "/usr/local/.c3os/db",
RootPath: "/",
Repository: "docker://quay.io/c3os/packages",
}
}
type BundleInstaller interface {
Install(*BundleConfig) error
}
// RunBundles runs bundles in a system.
// Accept a list of bundles options, which gets applied based on the bundle configuration.
func RunBundles(bundles ...[]BundleOption) error {
// TODO:
// - Make provider consume bundles when bins are not detected in the rootfs
// - Default bundles preset in case of no binaries detected and version specified via config.
var resErr error
for _, b := range bundles {
config := defaultConfig()
if err := config.Apply(b...); err != nil {
resErr = multierror.Append(err)
continue
}
installer, err := NewBundleInstaller(*config)
if err != nil {
resErr = multierror.Append(err)
continue
}
dat := strings.Split(config.Target, "://")
if len(dat) != 2 {
resErr = multierror.Append(fmt.Errorf("invalid target"))
continue
}
config.Target = dat[1]
err = installer.Install(config)
if err != nil {
resErr = multierror.Append(err)
continue
}
}
return resErr
}
func NewBundleInstaller(bc BundleConfig) (BundleInstaller, error) {
dat := strings.Split(bc.Target, "://")
if len(dat) != 2 {
return nil, fmt.Errorf("could not decode scheme")
}
switch strings.ToLower(dat[0]) {
case "container":
return &ContainerInstaller{}, nil
case "run":
return &ContainerRunner{}, nil
case "package":
return &LuetInstaller{}, nil
}
return &LuetInstaller{}, nil
}
// BundleInstall installs a bundle from a luet repo or a container image.
type ContainerRunner struct{}
func (l *ContainerRunner) Install(config *BundleConfig) error {
tempDir, err := ioutil.TempDir("", "containerrunner")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
out, err := utils.SH(
fmt.Sprintf(
`luet util unpack %s %s`,
config.Target,
tempDir,
),
)
if err != nil {
return fmt.Errorf("could not unpack container: %w - %s", err, out)
}
out, err = utils.SH(fmt.Sprintf("CONTAINERDIR=%s %s/run.sh", tempDir, tempDir))
if err != nil {
return fmt.Errorf("could not execute container: %w - %s", err, out)
}
return nil
}
type ContainerInstaller struct{}
func (l *ContainerInstaller) Install(config *BundleConfig) error {
//mkdir -p test/etc/luet/repos.conf.d
out, err := utils.SH(
fmt.Sprintf(
`luet util unpack %s %s`,
config.Target,
config.RootPath,
),
)
if err != nil {
return fmt.Errorf("could not unpack bundle: %w - %s", err, out)
}
return nil
}
type LuetInstaller struct{}
func (l *LuetInstaller) Install(config *BundleConfig) error {
t, repo, err := config.extractRepo()
if err != nil {
return err
}
err = os.MkdirAll(filepath.Join(config.RootPath, "etc/luet/repos.conf.d/"), os.ModePerm)
if err != nil {
return err
}
out, err := utils.SH(
fmt.Sprintf(
`LUET_CONFIG_FROM_HOST=false luet repo add --system-dbpath %s --system-target %s c3os-system -y --description "Automatically generated c3os-system" --url "%s" --type "%s"`,
config.DBPath,
config.RootPath,
repo,
t,
),
)
if err != nil {
return fmt.Errorf("could not add repository: %w - %s", err, out)
}
out, err = utils.SH(
fmt.Sprintf(
`LUET_CONFIG_FROM_HOST=false luet install -y --system-dbpath %s --system-target %s %s`,
config.DBPath,
config.RootPath,
config.Target,
),
)
if err != nil {
return fmt.Errorf("could not install bundle: %w - %s", err, out)
}
// copy bins to /usr/local/bin
return nil
}

View File

@ -1,157 +0,0 @@
package machine
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/c3os-io/c3os/internal/machine/openrc"
"github.com/c3os-io/c3os/internal/machine/systemd"
"github.com/denisbrodbeck/machineid"
"github.com/c3os-io/c3os/internal/utils"
)
type Service interface {
WriteUnit() error
Start() error
OverrideCmd(string) error
Enable() error
Restart() error
}
const (
PassiveBoot = "passive"
ActiveBoot = "active"
RecoveryBoot = "recovery"
LiveCDBoot = "liveCD"
NetBoot = "netboot"
UnknownBoot = "unknown"
)
// BootFrom returns the booting partition of the SUT.
func BootFrom() string {
out, err := utils.SH("cat /proc/cmdline")
if err != nil {
return UnknownBoot
}
switch {
case strings.Contains(out, "COS_ACTIVE"):
return ActiveBoot
case strings.Contains(out, "COS_PASSIVE"):
return PassiveBoot
case strings.Contains(out, "COS_RECOVERY"), strings.Contains(out, "COS_SYSTEM"):
return RecoveryBoot
case strings.Contains(out, "live:CDLABEL"):
return LiveCDBoot
case strings.Contains(out, "netboot"):
return NetBoot
default:
return UnknownBoot
}
}
func EdgeVPN(instance, rootDir string) (Service, error) {
if utils.IsOpenRCBased() {
return openrc.NewService(
openrc.WithName("edgevpn"),
openrc.WithUnitContent(openrc.EdgevpnUnit),
openrc.WithRoot(rootDir),
)
}
return systemd.NewService(
systemd.WithName("edgevpn"),
systemd.WithInstance(instance),
systemd.WithUnitContent(systemd.EdgevpnUnit),
systemd.WithRoot(rootDir),
)
}
const EdgeVPNDefaultInstance string = "c3os"
type fakegetty struct{}
func (fakegetty) Restart() error { return nil }
func (fakegetty) Enable() error { return nil }
func (fakegetty) OverrideCmd(string) error { return nil }
func (fakegetty) SetEnvFile(string) error { return nil }
func (fakegetty) WriteUnit() error { return nil }
func (fakegetty) Start() error {
utils.SH("chvt 2") //nolint:errcheck
return nil
}
func Getty(i int) (Service, error) {
if utils.IsOpenRCBased() {
return &fakegetty{}, nil
}
return systemd.NewService(
systemd.WithName("getty"),
systemd.WithInstance(fmt.Sprintf("tty%d", i)),
)
}
func K3s() (Service, error) {
if utils.IsOpenRCBased() {
return openrc.NewService(
openrc.WithName("k3s"),
)
}
return systemd.NewService(
systemd.WithName("k3s"),
)
}
func K3sAgent() (Service, error) {
if utils.IsOpenRCBased() {
return openrc.NewService(
openrc.WithName("k3s-agent"),
)
}
return systemd.NewService(
systemd.WithName("k3s-agent"),
)
}
func K3sEnvUnit(unit string) string {
if utils.IsOpenRCBased() {
return fmt.Sprintf("/etc/rancher/k3s/%s.env", unit)
}
return fmt.Sprintf("/etc/sysconfig/%s", unit)
}
func UUID() string {
if os.Getenv("UUID") != "" {
return os.Getenv("UUID")
}
id, _ := machineid.ID()
hostname, _ := os.Hostname()
return fmt.Sprintf("%s-%s", id, hostname)
}
func CreateSentinel(f string) error {
return ioutil.WriteFile(fmt.Sprintf("/usr/local/.c3os/sentinel_%s", f), []byte{}, os.ModePerm)
}
func SentinelExist(f string) bool {
if _, err := os.Stat(fmt.Sprintf("/usr/local/.c3os/sentinel_%s", f)); err == nil {
return true
}
return false
}
func ExecuteInlineCloudConfig(cloudConfig, stage string) error {
_, err := utils.ShellSTDIN(cloudConfig, fmt.Sprintf("elemental run-stage -s %s -", stage))
return err
}
func ExecuteCloudConfig(file, stage string) error {
_, err := utils.SH(fmt.Sprintf("elemental run-stage -s %s %s", stage, file))
return err
}

View File

@ -1,13 +0,0 @@
package machine_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestInstaller(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Machine Suite")
}

View File

@ -1,19 +0,0 @@
package openrc
const EdgevpnUnit string = `#!/sbin/openrc-run
depend() {
after net
provide edgevpn
}
supervisor=supervise-daemon
name="edgevpn"
command="edgevpn"
supervise_daemon_args="--stdout /var/log/edgevpn.log --stderr /var/log/edgevpn.log"
pidfile="/run/edgevpn.pid"
respawn_delay=5
set -o allexport
if [ -f /etc/environment ]; then source /etc/environment; fi
if [ -f /etc/systemd/system.conf.d/edgevpn-c3os.env ]; then source /etc/systemd/system.conf.d/edgevpn-c3os.env; fi
set +o allexport`

View File

@ -1,98 +0,0 @@
package openrc
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/c3os-io/c3os/internal/utils"
)
type ServiceUnit struct {
content string
name string
rootdir string
}
type ServiceOpts func(*ServiceUnit) error
func WithRoot(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.rootdir = n
return nil
}
}
func WithName(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.name = n
return nil
}
}
func WithUnitContent(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.content = n
return nil
}
}
func NewService(opts ...ServiceOpts) (ServiceUnit, error) {
s := &ServiceUnit{}
for _, o := range opts {
if err := o(s); err != nil {
return *s, err
}
}
return *s, nil
}
func (s ServiceUnit) WriteUnit() error {
uname := s.name
if err := ioutil.WriteFile(filepath.Join(s.rootdir, fmt.Sprintf("/etc/init.d/%s", uname)), []byte(s.content), 0755); err != nil {
return err
}
return nil
}
// TODO: This is too much k3s specific.
func (s ServiceUnit) OverrideCmd(cmd string) error {
k3sbin := utils.K3sBin()
if k3sbin == "" {
return fmt.Errorf("no k3s binary found (?)")
}
cmd = strings.ReplaceAll(cmd, k3sbin+" ", "")
envFile := filepath.Join(s.rootdir, fmt.Sprintf("/etc/rancher/k3s/%s.env", s.name))
env := make(map[string]string)
env["command_args"] = fmt.Sprintf("%s >>/var/log/%s.log 2>&1", cmd, s.name)
return utils.WriteEnv(envFile, env)
}
func (s ServiceUnit) Start() error {
out, err := utils.SH(fmt.Sprintf("/etc/init.d/%s start", s.name))
if err != nil {
return fmt.Errorf("failed starting service: %s. %s (%w)", s.name, out, err)
}
return nil
}
func (s ServiceUnit) Restart() error {
out, err := utils.SH(fmt.Sprintf("/etc/init.d/%s restart", s.name))
if err != nil {
return fmt.Errorf("failed restarting service: %s. %s (%w)", s.name, out, err)
}
return nil
}
func (s ServiceUnit) Enable() error {
_, err := utils.SH(fmt.Sprintf("ln -sf /etc/init.d/%s /etc/runlevels/default/%s", s.name, s.name))
return err
}
func (s ServiceUnit) StartBlocking() error {
return s.Start()
}

View File

@ -1,12 +0,0 @@
package systemd
const EdgevpnUnit string = `[Unit]
Description=EdgeVPN Daemon
After=network.target
[Service]
EnvironmentFile=/etc/systemd/system.conf.d/edgevpn-%i.env
LimitNOFILE=49152
ExecStart=edgevpn
Restart=always
[Install]
WantedBy=multi-user.target`

View File

@ -1,115 +0,0 @@
package systemd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/c3os-io/c3os/internal/utils"
)
type ServiceUnit struct {
content string
name, instance string
rootdir string
}
const overrideCmdTemplate string = `
[Service]
ExecStart=
ExecStart=%s
`
type ServiceOpts func(*ServiceUnit) error
func WithRoot(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.rootdir = n
return nil
}
}
func WithName(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.name = n
return nil
}
}
func WithInstance(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.instance = n
return nil
}
}
func WithUnitContent(n string) ServiceOpts {
return func(su *ServiceUnit) error {
su.content = n
return nil
}
}
func NewService(opts ...ServiceOpts) (ServiceUnit, error) {
s := &ServiceUnit{}
for _, o := range opts {
if err := o(s); err != nil {
return *s, err
}
}
return *s, nil
}
func (s ServiceUnit) WriteUnit() error {
uname := s.name
if s.instance != "" {
uname = fmt.Sprintf("%s@", s.name)
}
if err := ioutil.WriteFile(filepath.Join(s.rootdir, fmt.Sprintf("/etc/systemd/system/%s.service", uname)), []byte(s.content), 0600); err != nil {
return err
}
_, err := utils.SH("systemctl daemon-reload")
return err
}
func (s ServiceUnit) OverrideCmd(cmd string) error {
svcDir := filepath.Join(s.rootdir, fmt.Sprintf("/etc/systemd/system/%s.service.d/", s.name))
os.MkdirAll(svcDir, 0600) //nolint:errcheck
return ioutil.WriteFile(filepath.Join(svcDir, "override.conf"), []byte(fmt.Sprintf(overrideCmdTemplate, cmd)), 0600)
}
func (s ServiceUnit) Start() error {
return s.systemctl("start", false)
}
func (s ServiceUnit) Restart() error {
return s.systemctl("restart", false)
}
func (s ServiceUnit) Enable() error {
return s.systemctl("enable", false)
}
func (s ServiceUnit) StartBlocking() error {
return s.systemctl("start", true)
}
func (s ServiceUnit) systemctl(action string, blocking bool) error {
uname := s.name
if s.instance != "" {
uname = fmt.Sprintf("%s@%s", s.name, s.instance)
}
args := []string{action}
if !blocking {
args = append(args, "--no-block")
}
args = append(args, uname)
_, err := utils.SH(fmt.Sprintf("systemctl %s", strings.Join(args, " ")))
return err
}

View File

@ -1,221 +0,0 @@
package provider
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/c3os-io/c3os/sdk/bus"
logging "github.com/ipfs/go-log"
edgeVPNClient "github.com/mudler/edgevpn/api/client"
"go.uber.org/zap"
eventBus "github.com/c3os-io/c3os/internal/bus"
"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/pkg/config"
"github.com/mudler/edgevpn/api/client/service"
"github.com/mudler/go-pluggable"
)
func Bootstrap(e *pluggable.Event) pluggable.EventResponse {
cfg := &bus.BootstrapPayload{}
err := json.Unmarshal([]byte(e.Data), cfg)
if err != nil {
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 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 := (providerConfig.C3OS != nil && providerConfig.C3OS.NetworkToken == "")
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") //nolint:errcheck
eventBus.RunHookScript("/usr/bin/c3os-agent.bootstrap.hook") //nolint:errcheck
logLevel := "debug"
if providerConfig.C3OS != nil && providerConfig.C3OS.LogLevel != "" {
logLevel = providerConfig.C3OS.LogLevel
}
lvl, err := logging.LevelFromString(logLevel)
if err != nil {
return ErrorEvent("Failed setup logger: %s", err.Error())
}
// TODO: Fixup Logging to file
loggerCfg := zap.NewProductionConfig()
loggerCfg.OutputPaths = []string{
cfg.Logfile,
}
logger, err := loggerCfg.Build()
if err != nil {
return ErrorEvent("Failed setup logger: %s", err.Error())
}
logging.SetAllLoggers(lvl)
log := &logging.ZapEventLogger{SugaredLogger: *logger.Sugar()}
// 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 providerConfig.K3s.Enabled || providerConfig.K3sAgent.Enabled {
err := oneTimeBootstrap(log, providerConfig, func() error {
return SetupVPN(machine.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, providerConfig)
})
if err != nil {
return ErrorEvent("Failed setup: %s", err.Error())
}
return pluggable.EventResponse{}
} else if tokenNotDefined {
return ErrorEvent("No network token provided, exiting")
}
logger.Info("Configuring VPN")
if err := SetupVPN(machine.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, providerConfig); err != nil {
return ErrorEvent("Failed setup VPN: %s", err.Error())
}
networkID := "c3os"
if providerConfig.C3OS != nil && providerConfig.C3OS.NetworkID != "" {
networkID = providerConfig.C3OS.NetworkID
}
cc := service.NewClient(
networkID,
edgeVPNClient.NewClient(edgeVPNClient.WithHost(cfg.APIAddress)))
nodeOpts := []service.Option{
service.WithLogger(log),
service.WithClient(cc),
service.WithUUID(machine.UUID()),
service.WithStateDir("/usr/local/.c3os/state"),
service.WithNetworkToken(providerConfig.C3OS.NetworkToken),
service.WithPersistentRoles("auto"),
service.WithRoles(
service.RoleKey{
Role: "master",
RoleHandler: role.Master(c, providerConfig),
},
service.RoleKey{
Role: "worker",
RoleHandler: role.Worker(c, providerConfig),
},
service.RoleKey{
Role: "auto",
RoleHandler: role.Auto(c, providerConfig),
},
),
}
// Optionally set up a specific node role if the user has defined so
if providerConfig.C3OS.Role != "" {
nodeOpts = append(nodeOpts, service.WithDefaultRoles(providerConfig.C3OS.Role))
}
k, err := service.NewNode(nodeOpts...)
if err != nil {
return ErrorEvent("Failed creating node: %s", err.Error())
}
err = k.Start(context.Background())
if err != nil {
return ErrorEvent("Failed start: %s", err.Error())
}
return pluggable.EventResponse{
State: "",
Data: "",
Error: "shouldn't return here",
}
}
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
}
l.Info("One time bootstrap starting")
var svc machine.Service
k3sConfig := providerConfig.K3s{}
svcName := "k3s"
svcRole := "server"
if c.K3s.Enabled {
k3sConfig = c.K3s
} else if c.K3sAgent.Enabled {
k3sConfig = c.K3sAgent
svcName = "k3s-agent"
svcRole = "agent"
}
if utils.IsOpenRCBased() {
svc, _ = openrc.NewService(
openrc.WithName(svcName),
)
} else {
svc, _ = systemd.NewService(
systemd.WithName(svcName),
)
}
envFile := machine.K3sEnvUnit(svcName)
if svc == nil {
return fmt.Errorf("could not detect OS")
}
// Setup systemd unit and starts it
if err := utils.WriteEnv(envFile,
k3sConfig.Env,
); err != nil {
return err
}
k3sbin := utils.K3sBin()
if k3sbin == "" {
return fmt.Errorf("no k3s binary found (?)")
}
if err := svc.OverrideCmd(fmt.Sprintf("%s %s %s", k3sbin, svcRole, strings.Join(k3sConfig.Args, " "))); err != nil {
return err
}
if err := svc.Start(); err != nil {
return err
}
if err := svc.Enable(); err != nil {
return err
}
if len(c.VPN) > 0 {
if err := vpnSetupFN(); err != nil {
return err
}
}
return role.CreateSentinel()
}

View File

@ -1,54 +0,0 @@
package provider_test
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/c3os-io/c3os/sdk/bus"
. "github.com/c3os-io/c3os/internal/provider"
providerConfig "github.com/c3os-io/c3os/internal/provider/config"
"github.com/mudler/go-pluggable"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v2"
)
var _ = Describe("Bootstrap provider", func() {
Context("logging", func() {
e := &pluggable.Event{}
BeforeEach(func() {
e = &pluggable.Event{}
})
It("logs to file", func() {
f, err := ioutil.TempFile(os.TempDir(), "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f.Name())
cfg := &providerConfig.Config{
C3OS: &providerConfig.C3OS{
NetworkToken: "foo",
},
}
dat, err := yaml.Marshal(cfg)
Expect(err).ToNot(HaveOccurred())
payload := &bus.BootstrapPayload{Logfile: f.Name(), Config: string(dat)}
dat, err = json.Marshal(payload)
Expect(err).ToNot(HaveOccurred())
e.Data = string(dat)
resp := Bootstrap(e)
dat, _ = json.Marshal(resp)
Expect(resp.Errored()).To(BeTrue(), string(dat))
data, err := ioutil.ReadFile(f.Name())
Expect(err).ToNot(HaveOccurred())
Expect(string(data)).Should(ContainSubstring("Configuring VPN"))
})
})
})

View File

@ -1,38 +0,0 @@
package provider
import (
"encoding/json"
"github.com/c3os-io/c3os/sdk/bus"
providerConfig "github.com/c3os-io/c3os/internal/provider/config"
"github.com/c3os-io/c3os/pkg/config"
"github.com/mudler/go-nodepair"
"github.com/mudler/go-pluggable"
)
func Challenge(e *pluggable.Event) pluggable.EventResponse {
p := &bus.EventPayload{}
err := json.Unmarshal([]byte(e.Data), p)
if err != nil {
return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), e.Data)
}
cfg := &providerConfig.Config{}
err = config.FromString(p.Config, cfg)
if err != nil {
return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), p.Config)
}
tk := ""
if cfg.C3OS != nil && cfg.C3OS.NetworkToken != "" {
tk = cfg.C3OS.NetworkToken
}
if tk == "" {
tk = nodepair.GenerateToken()
}
return pluggable.EventResponse{
Data: tk,
}
}

View File

@ -1,72 +0,0 @@
package provider_test
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/c3os-io/c3os/sdk/bus"
providerConfig "github.com/c3os-io/c3os/internal/provider/config"
. "github.com/c3os-io/c3os/internal/provider"
"github.com/mudler/go-pluggable"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v2"
)
var _ = Describe("Challenge provider", func() {
Context("network token", func() {
e := &pluggable.Event{}
BeforeEach(func() {
e = &pluggable.Event{}
})
It("returns it if provided", func() {
f, err := ioutil.TempFile(os.TempDir(), "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f.Name())
cfg := &providerConfig.Config{
C3OS: &providerConfig.C3OS{
NetworkToken: "foo",
},
}
d, err := yaml.Marshal(cfg)
Expect(err).ToNot(HaveOccurred())
c := &bus.EventPayload{Config: string(d)}
dat, err := json.Marshal(c)
Expect(err).ToNot(HaveOccurred())
e.Data = string(dat)
resp := Challenge(e)
Expect(string(resp.Data)).Should(ContainSubstring("foo"))
})
It("generates it if not provided", func() {
f, err := ioutil.TempFile(os.TempDir(), "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f.Name())
cfg := &providerConfig.Config{
C3OS: &providerConfig.C3OS{
NetworkToken: "",
},
}
d, err := yaml.Marshal(cfg)
Expect(err).ToNot(HaveOccurred())
c := &bus.EventPayload{Config: string(d)}
dat, err := json.Marshal(c)
Expect(err).ToNot(HaveOccurred())
e.Data = string(dat)
resp := Challenge(e)
Expect(len(string(resp.Data))).Should(BeNumerically(">", 12))
})
})
})

View File

@ -1,11 +0,0 @@
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...)}
}

View File

@ -1,24 +0,0 @@
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"`
}

View File

@ -1,36 +0,0 @@
package provider
import (
"context"
"encoding/json"
"github.com/c3os-io/c3os/sdk/bus"
"github.com/mudler/go-nodepair"
"github.com/mudler/go-pluggable"
)
func Install(e *pluggable.Event) pluggable.EventResponse {
cfg := &bus.InstallPayload{}
err := json.Unmarshal([]byte(e.Data), cfg)
if err != nil {
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 ErrorEvent("Failed reading JSON input: %s", err.Error())
}
payload, err := json.Marshal(r)
if err != nil {
return ErrorEvent("Failed marshalling JSON input: %s", err.Error())
}
return pluggable.EventResponse{
State: "",
Data: string(payload),
Error: "",
}
}

View File

@ -1,13 +0,0 @@
package provider_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestInstaller(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Provider Suite")
}

View File

@ -1,92 +0,0 @@
package provider
import (
"fmt"
"os"
"path/filepath"
"strings"
"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 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")
}
svc, err := machine.EdgeVPN(instance, rootDir)
if err != nil {
return fmt.Errorf("could not create svc: %w", err)
}
apiAddress = strings.ReplaceAll(apiAddress, "https://", "")
apiAddress = strings.ReplaceAll(apiAddress, "http://", "")
vpnOpts := map[string]string{
"EDGEVPNTOKEN": c.C3OS.NetworkToken,
"API": "true",
"APILISTEN": apiAddress,
"EDGEVPNLOWPROFILEVPN": "true",
"DHCP": "true",
"DHCPLEASEDIR": "/usr/local/.c3os/lease",
}
// Override opts with user-supplied
for k, v := range c.VPN {
vpnOpts[k] = v
}
if c.C3OS.DNS {
vpnOpts["DNSADDRESS"] = "127.0.0.1:53"
vpnOpts["DNSFORWARD"] = "true"
if !utils.IsOpenRCBased() {
if _, err := os.Stat("/etc/sysconfig/network/config"); err == nil {
utils.WriteEnv("/etc/sysconfig/network/config", map[string]string{ //nolint:errcheck
"NETCONFIG_DNS_STATIC_SERVERS": "127.0.0.1",
})
if utils.Flavor() == "opensuse" {
// TODO: This is dependant on wickedd, move this out in its own network detection block
svc, err := systemd.NewService(systemd.WithName("wickedd"))
if err == nil {
svc.Restart() //nolint:errcheck
}
}
}
}
if err := config.SaveCloudConfig("dns", yip.YipConfig{
Name: "DNS Configuration",
Stages: map[string][]yip.Stage{
config.NetworkStage.String(): {{Dns: yip.DNS{Nameservers: []string{"127.0.0.1"}}}}},
}); err != nil {
return fmt.Errorf("could not create dns config: %w", err)
}
}
os.MkdirAll("/etc/systemd/system.conf.d/", 0600) //nolint:errcheck
// Setup edgevpn instance
err = utils.WriteEnv(filepath.Join(rootDir, "/etc/systemd/system.conf.d/edgevpn-c3os.env"), vpnOpts)
if err != nil {
return fmt.Errorf("could not create write env file: %w", err)
}
err = svc.WriteUnit()
if err != nil {
return fmt.Errorf("could not create write unit file: %w", err)
}
if start {
err = svc.Start()
if err != nil {
return fmt.Errorf("could not start svc: %w", err)
}
return svc.Enable()
}
return nil
}

View File

@ -1,62 +0,0 @@
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"
)
func contains(slice []string, elem string) bool {
for _, s := range slice {
if elem == s {
return true
}
}
return false
}
func Auto(cc *config.Config, pconfig *providerConfig.Config) Role {
return func(c *service.RoleConfig) error {
advertizing, _ := c.Client.AdvertizingNodes()
actives, _ := c.Client.ActiveNodes()
c.Logger.Info("Active nodes:", actives)
c.Logger.Info("Advertizing nodes:", advertizing)
if len(advertizing) < 2 {
c.Logger.Info("Not enough nodes")
return nil
}
// first get available nodes
nodes := advertizing
shouldBeLeader := utils.Leader(advertizing)
lead, _ := c.Client.Get("auto", "leader")
// From now on, only the leader keeps processing
// TODO: Make this more reliable with consensus
if shouldBeLeader != c.UUID && lead != c.UUID {
c.Logger.Infof("<%s> not a leader, leader is '%s', sleeping", c.UUID, shouldBeLeader)
return nil
}
if shouldBeLeader == c.UUID && (lead == "" || !contains(nodes, lead)) {
if err := c.Client.Set("auto", "leader", c.UUID); err != nil {
c.Logger.Error(err)
return err
}
c.Logger.Info("Announcing ourselves as leader, backing off")
return nil
}
if lead != c.UUID {
c.Logger.Info("Backing off, as we are not currently flagged as leader")
return nil
}
return scheduleRoles(nodes, c, cc, pconfig)
}
}

View File

@ -1,21 +0,0 @@
package role
import (
"io/ioutil"
"os"
service "github.com/mudler/edgevpn/api/client/service"
)
type Role func(*service.RoleConfig) error
func SentinelExist() bool {
if _, err := os.Stat("/usr/local/.c3os/deployed"); err == nil {
return true
}
return false
}
func CreateSentinel() error {
return ioutil.WriteFile("/usr/local/.c3os/deployed", []byte{}, os.ModePerm)
}

View File

@ -1,155 +0,0 @@
package role
import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/c3os-io/c3os/internal/machine"
"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"
)
func propagateMasterData(ip string, c *service.RoleConfig) error {
defer func() {
// Avoid polluting the API.
// The ledger already retries in the background to update the blockchain, but it has
// a default timeout where it would stop trying afterwards.
// Each request here would have it's own background announce, so that can become expensive
// when network is having lot of changes on its way.
time.Sleep(30 * time.Second)
}()
// If we are configured as master, always signal our role
if err := c.Client.Set("role", c.UUID, "master"); err != nil {
c.Logger.Error(err)
return err
}
tokenB, err := ioutil.ReadFile("/var/lib/rancher/k3s/server/node-token")
if err != nil {
c.Logger.Error(err)
return err
}
nodeToken := string(tokenB)
nodeToken = strings.TrimRight(nodeToken, "\n")
if nodeToken != "" {
err := c.Client.Set("nodetoken", "token", nodeToken)
if err != nil {
c.Logger.Error(err)
}
}
kubeB, err := ioutil.ReadFile("/etc/rancher/k3s/k3s.yaml")
if err != nil {
c.Logger.Error(err)
return err
}
kubeconfig := string(kubeB)
if kubeconfig != "" {
err := c.Client.Set("kubeconfig", "master", base64.RawURLEncoding.EncodeToString(kubeB))
if err != nil {
c.Logger.Error(err)
}
}
err = c.Client.Set("master", "ip", ip)
if err != nil {
c.Logger.Error(err)
}
return nil
}
func Master(cc *config.Config, pconfig *providerConfig.Config) Role {
return func(c *service.RoleConfig) error {
ip := utils.GetInterfaceIP("edgevpn0")
if ip == "" {
return errors.New("node doesn't have an ip yet")
}
if pconfig.C3OS.Role != "" {
// propagate role if we were forced by configuration
// This unblocks eventual auto instances to try to assign roles
if err := c.Client.Set("role", c.UUID, pconfig.C3OS.Role); err != nil {
c.Logger.Error(err)
}
}
if SentinelExist() {
c.Logger.Info("Node already configured, backing off")
return propagateMasterData(ip, c)
}
// Configure k3s service to start on edgevpn0
c.Logger.Info("Configuring k3s")
svc, err := machine.K3s()
if err != nil {
return err
}
k3sConfig := providerConfig.K3s{}
if pconfig.K3s.Enabled {
k3sConfig = pconfig.K3s
}
env := map[string]string{}
if !k3sConfig.ReplaceEnv {
// Override opts with user-supplied
for k, v := range k3sConfig.Env {
env[k] = v
}
} else {
env = k3sConfig.Env
}
if err := utils.WriteEnv(machine.K3sEnvUnit("k3s"),
env,
); err != nil {
return err
}
args := []string{"--flannel-iface=edgevpn0"}
if k3sConfig.ReplaceArgs {
args = k3sConfig.Args
} else {
args = append(args, k3sConfig.Args...)
}
k3sbin := utils.K3sBin()
if k3sbin == "" {
return fmt.Errorf("no k3s binary found (?)")
}
if err := svc.OverrideCmd(fmt.Sprintf("%s server %s", k3sbin, strings.Join(args, " "))); err != nil {
return err
}
if err := svc.Start(); err != nil {
return err
}
if err := svc.Enable(); err != nil {
return err
}
if err := propagateMasterData(ip, c); err != nil {
return err
}
return CreateSentinel()
}
}
// TODO: https://rancher.com/docs/k3s/latest/en/installation/ha-embedded/
func HAMaster(c *service.RoleConfig) {
c.Logger.Info("HA Role not implemented yet")
}

View File

@ -1,80 +0,0 @@
package role
import (
"math/rand"
"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, pconfig *providerConfig.Config) error {
rand.Seed(time.Now().Unix())
// Assign roles to nodes
currentRoles := map[string]string{}
existsMaster := false
unassignedNodes := []string{}
for _, a := range nodes {
role, _ := c.Client.Get("role", a)
currentRoles[a] = role
if role == "master" {
existsMaster = true
} else if role == "" {
unassignedNodes = append(unassignedNodes, a)
}
}
c.Logger.Infof("I'm the leader. My UUID is: %s.\n Current assigned roles: %+v", c.UUID, currentRoles)
c.Logger.Infof("Master already present: %t", existsMaster)
c.Logger.Infof("Unassigned nodes: %+v", unassignedNodes)
if !existsMaster && len(unassignedNodes) > 0 {
var selected string
toSelect := unassignedNodes
// Avoid to schedule to ourselves if we have a static role
if pconfig.C3OS.Role != "" {
toSelect = []string{}
for _, u := range unassignedNodes {
if u != c.UUID {
toSelect = append(toSelect, u)
}
}
}
// select one node without roles to become master
if len(toSelect) == 1 {
selected = toSelect[0]
} else {
selected = toSelect[rand.Intn(len(toSelect)-1)]
}
if err := c.Client.Set("role", selected, "master"); err != nil {
return err
}
c.Logger.Info("-> Set master to", selected)
currentRoles[selected] = "master"
// Return here, so next time we get called
// makes sure master is set.
return nil
}
// cycle all empty roles and assign worker roles
for _, uuid := range unassignedNodes {
if err := c.Client.Set("role", uuid, "worker"); err != nil {
c.Logger.Error(err)
return err
}
c.Logger.Info("-> Set worker to", uuid)
}
c.Logger.Info("Done scheduling")
return nil
}

View File

@ -1,113 +0,0 @@
package role
import (
"errors"
"fmt"
"strings"
"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"
service "github.com/mudler/edgevpn/api/client/service"
)
func Worker(cc *config.Config, pconfig *providerConfig.Config) Role {
return func(c *service.RoleConfig) error {
if pconfig.C3OS.Role != "" {
// propagate role if we were forced by configuration
// This unblocks eventual auto instances to try to assign roles
if err := c.Client.Set("role", c.UUID, pconfig.C3OS.Role); err != nil {
return err
}
}
if SentinelExist() {
c.Logger.Info("Node already configured, backing off")
return nil
}
masterIP, _ := c.Client.Get("master", "ip")
if masterIP == "" {
c.Logger.Info("MasterIP not there still..")
return nil
}
nodeToken, _ := c.Client.Get("nodetoken", "token")
if masterIP == "" {
c.Logger.Info("nodetoken not there still..")
return nil
}
nodeToken = strings.TrimRight(nodeToken, "\n")
ip := utils.GetInterfaceIP("edgevpn0")
if ip == "" {
return errors.New("node doesn't have an ip yet")
}
c.Logger.Info("Configuring k3s-agent", ip, masterIP, nodeToken)
svc, err := machine.K3sAgent()
if err != nil {
return err
}
k3sConfig := providerConfig.K3s{}
if pconfig.K3sAgent.Enabled {
k3sConfig = pconfig.K3sAgent
}
env := map[string]string{
"K3S_URL": fmt.Sprintf("https://%s:6443", masterIP),
"K3S_TOKEN": nodeToken,
}
if !k3sConfig.ReplaceEnv {
// Override opts with user-supplied
for k, v := range k3sConfig.Env {
env[k] = v
}
} else {
env = k3sConfig.Env
}
// Setup systemd unit and starts it
if err := utils.WriteEnv(machine.K3sEnvUnit("k3s-agent"),
env,
); err != nil {
return err
}
args := []string{
"--with-node-id",
fmt.Sprintf("--node-ip %s", ip),
"--flannel-iface=edgevpn0",
}
if k3sConfig.ReplaceArgs {
args = k3sConfig.Args
} else {
args = append(args, k3sConfig.Args...)
}
k3sbin := utils.K3sBin()
if k3sbin == "" {
return fmt.Errorf("no k3s binary found (?)")
}
if err := svc.OverrideCmd(fmt.Sprintf("%s agent %s", k3sbin, strings.Join(args, " "))); err != nil {
return err
}
if err := svc.Start(); err != nil {
return err
}
if err := svc.Enable(); err != nil {
return err
}
return CreateSentinel()
}
}

View File

@ -1,37 +0,0 @@
package utils
import (
"bufio"
"bytes"
"fmt"
"image"
"os"
"strings"
"github.com/pterm/pterm"
"github.com/qeesung/image2ascii/convert"
)
func Prompt(t string) (string, error) {
if t != "" {
pterm.Info.Println(t)
}
answer, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(answer), nil
}
func PrintBanner(d []byte) {
img, _, _ := image.Decode(bytes.NewReader(d))
convertOptions := convert.DefaultOptions
convertOptions.FixedWidth = 100
convertOptions.FixedHeight = 40
// Create the image converter
converter := convert.NewImageConverter()
fmt.Print(converter.Image2ASCIIString(img, &convertOptions))
}

View File

@ -1,43 +0,0 @@
package utils
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"github.com/joho/godotenv"
)
func SH(c string) (string, error) {
o, err := exec.Command("/bin/sh", "-c", c).CombinedOutput()
return string(o), err
}
func WriteEnv(envFile string, config map[string]string) error {
content, _ := ioutil.ReadFile(envFile)
env, _ := godotenv.Unmarshal(string(content))
for key, val := range config {
env[key] = val
}
return godotenv.Write(env, envFile)
}
func Shell() *exec.Cmd {
cmd := exec.Command("/bin/sh")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
return cmd
}
func ShellSTDIN(s, c string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", c)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = bytes.NewBuffer([]byte(s))
o, err := cmd.CombinedOutput()
return string(o), err
}

View File

@ -1,15 +0,0 @@
package utils
import (
"os"
"os/signal"
)
func OnSignal(fn func(), sig ...os.Signal) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, sig...)
go func() {
<-sigs
fn()
}()
}

View File

@ -1,20 +0,0 @@
package utils
import (
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

View File

@ -1,94 +0,0 @@
package utils
import (
"fmt"
"net"
"os"
"strings"
"github.com/joho/godotenv"
"github.com/pterm/pterm"
)
func Reboot() {
pterm.Info.Println("Rebooting node")
SH("reboot") //nolint:errcheck
}
func PowerOFF() {
pterm.Info.Println("Shutdown node")
if IsOpenRCBased() {
SH("poweroff") //nolint:errcheck
} else {
SH("shutdown") //nolint:errcheck
}
}
func Version() string {
release, _ := godotenv.Read("/etc/os-release")
v := release["VERSION"]
v = strings.ReplaceAll(v, "+k3s1-c3OS", "-")
v = strings.ReplaceAll(v, "+k3s-c3OS", "-")
return strings.ReplaceAll(v, "c3OS", "")
}
func OSRelease(key string) (string, error) {
release, err := godotenv.Read("/etc/os-release")
if err != nil {
return "", err
}
v, exists := release[key]
if !exists {
return "", fmt.Errorf("key not found")
}
return v, nil
}
func Flavor() string {
release, _ := godotenv.Read("/etc/os-release")
v := release["NAME"]
return strings.ReplaceAll(v, "c3os-", "")
}
func IsOpenRCBased() bool {
f := Flavor()
return f == "alpine" || f == "alpine-arm-rpi"
}
func GetInterfaceIP(in string) string {
ifaces, err := net.Interfaces()
if err != nil {
fmt.Println("failed getting system interfaces")
return ""
}
for _, i := range ifaces {
if i.Name == in {
addrs, _ := i.Addrs()
// handle err
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip != nil {
return ip.String()
}
}
}
}
return ""
}
func K3sBin() string {
for _, p := range []string{"/usr/bin/k3s", "/usr/local/bin/k3s"} {
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}

View File

@ -1,13 +0,0 @@
package utils
import "strings"
const sep = "_CREDENTIALS_"
func EncodeRecoveryToken(data ...string) string {
return strings.Join(data, sep)
}
func DecodeRecoveryToken(recoverytoken string) []string {
return strings.Split(recoverytoken, sep)
}