hgctl/pkg/installer/standalone_agent.go (295 lines of code) (raw):

// Copyright (c) 2022 Alibaba Group Holding Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package installer import ( "bytes" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "strings" "time" "github.com/alibaba/higress/hgctl/pkg/helm" ) type RunSudoState string const ( NoSudo RunSudoState = "NoSudo" SudoWithoutPassword RunSudoState = "SudoWithoutPassword" SudoWithPassword RunSudoState = "SudoWithPassword" ) type Agent struct { profile *helm.Profile writer io.Writer shutdownBinaryName string resetBinaryName string startupBinaryName string installBinaryName string installPath string configuredPath string higressPath string versionPath string quiet bool runSudoState RunSudoState } func NewAgent(profile *helm.Profile, writer io.Writer, quiet bool) *Agent { installPath := profile.InstallPackagePath return &Agent{ profile: profile, writer: writer, installPath: installPath, higressPath: filepath.Join(installPath, "higress"), installBinaryName: filepath.Join(installPath, "get-higress.sh"), shutdownBinaryName: filepath.Join(installPath, "higress", "bin", "shutdown.sh"), resetBinaryName: filepath.Join(installPath, "higress", "bin", "reset.sh"), startupBinaryName: filepath.Join(installPath, "higress", "bin", "startup.sh"), configuredPath: filepath.Join(installPath, "higress", "compose", ".configured"), versionPath: filepath.Join(installPath, "higress", "VERSION"), quiet: quiet, runSudoState: NoSudo, } } func (a *Agent) profileArgs() []string { args := []string{ fmt.Sprintf("--nacos-ns=%s", a.profile.Storage.Ns), fmt.Sprintf("--config-url=%s", a.profile.Storage.Url), fmt.Sprintf("--nacos-ns=%s", a.profile.Storage.Ns), fmt.Sprintf("--nacos-password=%s", a.profile.Storage.Password), fmt.Sprintf("--nacos-username=%s", a.profile.Storage.Username), fmt.Sprintf("--data-enc-key=%s", a.profile.Storage.DataEncKey), fmt.Sprintf("--console-port=%d", a.profile.Console.Port), fmt.Sprintf("--gateway-http-port=%d", a.profile.Gateway.HttpPort), fmt.Sprintf("--gateway-https-port=%d", a.profile.Gateway.HttpsPort), fmt.Sprintf("--gateway-metrics-port=%d", a.profile.Gateway.MetricsPort), } return args } func (a *Agent) run(binaryName string, args []string, autoSudo bool) error { var cmd *exec.Cmd if !autoSudo || a.runSudoState == NoSudo { if !a.quiet { fmt.Fprintf(a.writer, "\n📦 Running command: %s %s\n\n", binaryName, strings.Join(args, " ")) } cmd = exec.Command(binaryName, args...) } else { newArgs := make([]string, 0) newArgs = append(newArgs, binaryName) newArgs = append(newArgs, args...) if !a.quiet { fmt.Fprintf(a.writer, "\n📦 Running command: %s %s\n\n", "sudo", strings.Join(newArgs, " ")) } cmd = exec.Command("sudo", newArgs...) } cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = a.installPath if err := cmd.Start(); err != nil { return err } done := make(chan error, 1) go func() { done <- cmd.Wait() }() select { case err := <-done: return err } return nil } func (a *Agent) checkSudoPermission() error { if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Checking docker command sudo permission... ") } // check docker ps command cmd := exec.Command("docker", "ps") var out bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &out cmd.Stderr = &stderr cmd.Dir = a.installPath if err := cmd.Start(); err != nil { return err } done := make(chan error, 1) go func() { done <- cmd.Wait() }() select { case err := <-done: if err == nil { if !a.quiet { fmt.Fprintf(a.writer, "checked result: no need sudo permission\n") } a.runSudoState = NoSudo return nil } } // check sudo docker ps command cmd2 := exec.Command("sudo", "-S", "docker", "ps") var out2 bytes.Buffer var stderr2 bytes.Buffer cmd2.Stdout = &out2 cmd2.Stderr = &stderr2 cmd2.Dir = a.installPath stdin, _ := cmd2.StdinPipe() defer stdin.Close() if err := cmd2.Start(); err != nil { return err } done2 := make(chan error, 1) go func() { done2 <- cmd2.Wait() }() select { case <-time.After(5 * time.Second): cmd2.Process.Signal(os.Interrupt) if !a.quiet { fmt.Fprintf(a.writer, "checked result: timeout exceed and need sudo with password\n") } a.runSudoState = SudoWithPassword case err := <-done2: if err == nil { if !a.quiet { fmt.Fprintf(a.writer, "checked result: need sudo without password\n") } a.runSudoState = SudoWithoutPassword } else { if !a.quiet { fmt.Fprintf(a.writer, "checked result: need sudo with password\n") } a.runSudoState = SudoWithPassword } } return nil } func (a *Agent) Install() error { a.checkSudoPermission() if a.runSudoState == SudoWithPassword { if !a.promptSudo() { return errors.New("cancel installation") } } if a.hasConfigured() { a.Reset() } if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Starting to install higress.. \n") } args := []string{"./higress"} args = append(args, a.profileArgs()...) return a.run(a.installBinaryName, args, true) return nil } func (a *Agent) Uninstall() error { a.checkSudoPermission() if a.runSudoState == SudoWithPassword { if !a.promptSudo() { return errors.New("cancel uninstall") } } if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Starting to uninstall higress... \n") } if err := a.Reset(); err != nil { return err } return nil } func (a *Agent) Upgrade() error { a.checkSudoPermission() if a.runSudoState == SudoWithPassword { if !a.promptSudo() { return errors.New("cancel upgrade") } } currentVersion := "" newVersion := "" if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Checking current higress version... ") currentVersion, _ = a.Version() fmt.Fprintf(a.writer, "%s\n", currentVersion) } if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Starting to upgrade higress... \n") } if err := a.run(a.installBinaryName, []string{"-u"}, true); err != nil { return err } if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Checking new higress version... ") newVersion, _ = a.Version() fmt.Fprintf(a.writer, "%s\n", newVersion) } if currentVersion == newVersion { return nil } if !a.promptRestart() { return nil } if err := a.Shutdown(); err != nil { return err } if err := a.Startup(); err != nil { return err } return nil } func (a *Agent) Version() (string, error) { version := "" content, err := os.ReadFile(a.versionPath) if err != nil { return version, nil } return string(content), nil } func (a *Agent) promptSudo() bool { answer := "" for { fmt.Fprintf(a.writer, "\nThis need sudo permission and input root password to continue installation, Proceed? (y/N)") fmt.Scanln(&answer) if strings.TrimSpace(answer) == "y" { fmt.Fprintf(a.writer, "\n") return true } if strings.TrimSpace(answer) == "N" { fmt.Fprintf(a.writer, "Cancelled.\n") return false } } } func (a *Agent) promptRestart() bool { answer := "" for { fmt.Fprintf(a.writer, "\nThis need to restart higress, Proceed? (y/N)") fmt.Scanln(&answer) if strings.TrimSpace(answer) == "y" { fmt.Fprintf(a.writer, "\n") return true } if strings.TrimSpace(answer) == "N" { fmt.Fprintf(a.writer, "Cancelled.\n") return false } } } func (a *Agent) Startup() error { if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Starting higress... \n") } return a.run(a.startupBinaryName, []string{}, true) } func (a *Agent) Shutdown() error { if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Shutdowning higress... \n") } return a.run(a.shutdownBinaryName, []string{}, true) } func (a *Agent) Reset() error { if !a.quiet { fmt.Fprintf(a.writer, "\n⌛️ Resetting higress....\n") } return a.run(a.resetBinaryName, []string{}, true) } func (a *Agent) hasConfigured() bool { if _, err := os.Stat(a.configuredPath); os.IsNotExist(err) { return false } return true }