mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-04-28 03:32:27 +00:00
art: Drop provider from c3os code
Part of: https://github.com/c3os-io/c3os/issues/68
This commit is contained in:
parent
b19a93e984
commit
3c6484de14
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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{
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
@ -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())
|
||||
})
|
||||
})
|
||||
})
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
@ -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`
|
@ -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()
|
||||
}
|
@ -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`
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
@ -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,
|
||||
}
|
||||
}
|
@ -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))
|
||||
})
|
||||
})
|
||||
})
|
@ -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...)}
|
||||
}
|
@ -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"`
|
||||
}
|
@ -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: "",
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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()
|
||||
}()
|
||||
}
|
@ -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)
|
||||
}
|
@ -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 ""
|
||||
}
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user