feat: add worker install and deploy
Все проверки выполнены успешно
CI / test (push) Successful in 2m24s
Docker / Build and publish worker image (push) Successful in 13m24s
Все проверки выполнены успешно
CI / test (push) Successful in 2m24s
Docker / Build and publish worker image (push) Successful in 13m24s
Этот коммит содержится в:
@@ -50,6 +50,9 @@ var (
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
if handled, code := dispatchManagementCommand(os.Args[1:]); handled {
|
||||
os.Exit(code)
|
||||
}
|
||||
loadDotEnv()
|
||||
|
||||
versionFlag := flag.Bool("version", false, "Print version and exit")
|
||||
@@ -71,6 +74,10 @@ func main() {
|
||||
if len(flag.Args()) > 0 && flag.Arg(0) == "liveness" {
|
||||
os.Exit(livenessCheck())
|
||||
}
|
||||
if len(flag.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "unknown command %q\n", flag.Arg(0))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
log.Println("rsmon-worker starting...")
|
||||
|
||||
|
||||
@@ -3,9 +3,30 @@ package main
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDispatchManagementCommand(t *testing.T) {
|
||||
if handled, _ := dispatchManagementCommand(nil); handled {
|
||||
t.Fatal("empty arguments were handled")
|
||||
}
|
||||
if handled, code := dispatchManagementCommand([]string{"install", "--help"}); !handled || code != 0 {
|
||||
t.Fatalf("install help = handled %t code %d", handled, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretValue(t *testing.T) {
|
||||
path := t.TempDir() + "/secret"
|
||||
if err := os.WriteFile(path, []byte("value\n"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := secretValue("", path)
|
||||
if err != nil || got != "value" {
|
||||
t.Fatalf("secretValue() = %q, %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeLiveness(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
145
cmd/rsmon-worker/management.go
Обычный файл
145
cmd/rsmon-worker/management.go
Обычный файл
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"rocketgit.ru/rsmon/worker/internal/installer"
|
||||
)
|
||||
|
||||
func dispatchManagementCommand(args []string) (bool, int) {
|
||||
if len(args) == 0 {
|
||||
return false, 0
|
||||
}
|
||||
switch args[0] {
|
||||
case "install":
|
||||
return true, installCommand(args[1:])
|
||||
case "deploy":
|
||||
return true, deployCommand(args[1:])
|
||||
default:
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
|
||||
func installCommand(args []string) int {
|
||||
fs := flag.NewFlagSet("install", flag.ContinueOnError)
|
||||
fs.SetOutput(os.Stderr)
|
||||
var opts installer.InstallOptions
|
||||
var tokenFile string
|
||||
fs.StringVar(&opts.Binary, "binary", "", "worker binary to install (default: this executable)")
|
||||
fs.StringVar(&opts.EnvFile, "env-file", "", "existing worker environment file")
|
||||
fs.StringVar(&opts.Token, "token", "", "worker API token")
|
||||
fs.StringVar(&opts.Token, "api-key", "", "worker API token (alias for --token)")
|
||||
fs.StringVar(&tokenFile, "token-file", "", "file containing the worker API token")
|
||||
fs.StringVar(&opts.URL, "url", installer.DefaultURL, "RSMon server URL")
|
||||
fs.BoolVar(&opts.Docker, "docker", false, "run the prebuilt Docker image instead of the binary")
|
||||
fs.StringVar(&opts.Image, "image", installer.DefaultImage, "Docker image used with --docker")
|
||||
fs.BoolVar(&opts.NoStart, "no-start", false, "install and enable without starting")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(fs.Output(), "Usage: rsmon-worker install --token TOKEN [--url URL] [--no-start]")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
fs.Usage()
|
||||
return 2
|
||||
}
|
||||
var err error
|
||||
if opts.Token, err = secretValue(opts.Token, tokenFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "install failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if err := installer.Install(opts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "install failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(os.Stdout, "rsmon-worker installed")
|
||||
return 0
|
||||
}
|
||||
|
||||
func deployCommand(args []string) int {
|
||||
fs := flag.NewFlagSet("deploy", flag.ContinueOnError)
|
||||
fs.SetOutput(os.Stderr)
|
||||
var opts installer.DeployOptions
|
||||
var tokenFile, passwordFile, passphraseFile, sudoPasswordFile string
|
||||
fs.StringVar(&opts.Host, "host", "", "SSH server hostname or address")
|
||||
fs.IntVar(&opts.Port, "port", 22, "SSH server port")
|
||||
fs.StringVar(&opts.User, "user", "", "SSH username")
|
||||
fs.StringVar(&opts.IdentityFile, "identity-file", "", "SSH private key path")
|
||||
fs.StringVar(&opts.KeyPassphrase, "key-passphrase", "", "SSH private key passphrase")
|
||||
fs.StringVar(&passphraseFile, "key-passphrase-file", "", "file containing the private key passphrase")
|
||||
fs.StringVar(&opts.Password, "password", "", "SSH login password")
|
||||
fs.StringVar(&passwordFile, "password-file", "", "file containing the SSH login password")
|
||||
fs.StringVar(&opts.SudoPassword, "sudo-password", "", "remote sudo password")
|
||||
fs.StringVar(&sudoPasswordFile, "sudo-password-file", "", "file containing the remote sudo password")
|
||||
fs.StringVar(&opts.KnownHostsFile, "known-hosts", "", "known_hosts path (default: ~/.ssh/known_hosts)")
|
||||
fs.StringVar(&opts.HostKeyFingerprint, "host-key-fingerprint", "", "expected SHA256 SSH host-key fingerprint")
|
||||
fs.BoolVar(&opts.InsecureHostKey, "insecure-host-key", false, "disable SSH host-key verification (unsafe)")
|
||||
fs.StringVar(&opts.Binary, "binary", "", "worker binary to upload (default: this executable)")
|
||||
fs.StringVar(&opts.Token, "token", "", "worker API token")
|
||||
fs.StringVar(&opts.Token, "api-key", "", "worker API token (alias for --token)")
|
||||
fs.StringVar(&tokenFile, "token-file", "", "file containing the worker API token")
|
||||
fs.StringVar(&opts.URL, "url", installer.DefaultURL, "RSMon server URL")
|
||||
fs.BoolVar(&opts.Docker, "docker", false, "install the prebuilt Docker image remotely")
|
||||
fs.StringVar(&opts.Image, "image", installer.DefaultImage, "Docker image used with --docker")
|
||||
fs.BoolVar(&opts.NoStart, "no-start", false, "install and enable without starting")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(fs.Output(), "Usage: rsmon-worker deploy --host HOST --user USER --token TOKEN [SSH options]")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
fs.Usage()
|
||||
return 2
|
||||
}
|
||||
var err error
|
||||
if opts.Token, err = secretValue(opts.Token, tokenFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "deploy failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if opts.Password, err = secretValue(opts.Password, passwordFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "deploy failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if opts.KeyPassphrase, err = secretValue(opts.KeyPassphrase, passphraseFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "deploy failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if opts.SudoPassword, err = secretValue(opts.SudoPassword, sudoPasswordFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "deploy failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if err := installer.Deploy(opts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "deploy failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(os.Stdout, "rsmon-worker deployed")
|
||||
return 0
|
||||
}
|
||||
|
||||
func secretValue(direct, path string) (string, error) {
|
||||
if direct != "" && path != "" {
|
||||
return "", fmt.Errorf("a secret and its file option cannot both be set")
|
||||
}
|
||||
if path == "" {
|
||||
return direct, nil
|
||||
}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read secret file: %w", err)
|
||||
}
|
||||
return strings.TrimRight(string(b), "\r\n"), nil
|
||||
}
|
||||
Ссылка в новой задаче
Block a user