mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 05:01:19 +08:00
357 lines
8.4 KiB
Go
357 lines
8.4 KiB
Go
// 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
|
||
}
|