mirror of
https://github.com/alibaba/higress.git
synced 2026-05-05 02:47:26 +08:00
feat:add installation for higress standalone in local docker environment (#606)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
This commit is contained in:
108
pkg/cmd/hgctl/installer/gateway_api.go
Normal file
108
pkg/cmd/hgctl/installer/gateway_api.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
|
||||
)
|
||||
|
||||
const (
|
||||
GatewayAPI ComponentName = "gatewayAPI"
|
||||
)
|
||||
|
||||
type GatewayAPIComponent struct {
|
||||
profile *helm.Profile
|
||||
started bool
|
||||
opts *ComponentOptions
|
||||
renderer helm.Renderer
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewGatewayAPIComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
|
||||
newOpts := &ComponentOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(newOpts)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(newOpts.RepoURL, "embed://") {
|
||||
return nil, errors.New("GatewayAPI Url need start with embed://")
|
||||
}
|
||||
|
||||
chartDir := strings.TrimPrefix(newOpts.RepoURL, "embed://")
|
||||
// GatewayAPI can only be installed by embed type
|
||||
renderer, err := helm.NewLocalFileRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(manifests.BuiltinOrDir("")),
|
||||
helm.WithDir(chartDir),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gatewayAPIComponent := &GatewayAPIComponent{
|
||||
profile: profile,
|
||||
renderer: renderer,
|
||||
opts: newOpts,
|
||||
writer: writer,
|
||||
}
|
||||
return gatewayAPIComponent, nil
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) ComponentName() ComponentName {
|
||||
return GatewayAPI
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) Namespace() string {
|
||||
return i.opts.Namespace
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) Run() error {
|
||||
if !i.opts.Quiet {
|
||||
fmt.Fprintf(i.writer, "🏄 Downloading GatewayAPI Yaml Files version: %s, url: %s\n", i.opts.Version, i.opts.RepoURL)
|
||||
}
|
||||
if err := i.renderer.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
i.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) RenderManifest() (string, error) {
|
||||
if !i.started {
|
||||
return "", nil
|
||||
}
|
||||
if !i.opts.Quiet {
|
||||
fmt.Fprintf(i.writer, "📦 Rendering GatewayAPI Yaml Files\n")
|
||||
}
|
||||
values := make(map[string]any)
|
||||
manifest, err := renderComponentManifest(values, i.renderer, false, i.ComponentName(), i.opts.Namespace)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
@@ -15,11 +15,10 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -96,29 +95,19 @@ func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...Compon
|
||||
opt(newOpts)
|
||||
}
|
||||
|
||||
var renderer helm.Renderer
|
||||
var err error
|
||||
if newOpts.RepoURL != "" {
|
||||
renderer, err = helm.NewRemoteRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
renderer, err = helm.NewLocalRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
||||
helm.WithDir(string(Higress)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(newOpts.RepoURL) == 0 {
|
||||
return nil, errors.New("Higress helm chart url can't be empty")
|
||||
}
|
||||
|
||||
// Higress can only be installed by remote type
|
||||
renderer, err := helm.NewRemoteRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
higressComponent := &HigressComponent{
|
||||
|
||||
@@ -18,196 +18,113 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||
"github.com/alibaba/higress/pkg/cmd/options"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
type Installer struct {
|
||||
started bool
|
||||
components map[ComponentName]Component
|
||||
kubeCli kubernetes.CLIClient
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
const (
|
||||
HgctlHomeDirPath = ".hgctl"
|
||||
StandaloneInstalledPath = "higress-standalone"
|
||||
ProfileInstalledPath = "profiles"
|
||||
InstalledYamlFileName = "install.yaml"
|
||||
DefaultGatewayAPINamespace = "gateway-system"
|
||||
DefaultIstioNamespace = "istio-system"
|
||||
)
|
||||
|
||||
type Installer interface {
|
||||
Install() error
|
||||
UnInstall() error
|
||||
Upgrade() error
|
||||
}
|
||||
|
||||
// Run must be invoked before invoking other functions.
|
||||
func (o *Installer) Run() error {
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
if err := component.Run(); err != nil {
|
||||
return fmt.Errorf("component %s run failed, err: %s", name, err)
|
||||
}
|
||||
}
|
||||
o.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderManifests renders component manifests specified by profile.
|
||||
func (o *Installer) RenderManifests() (map[ComponentName]string, error) {
|
||||
if !o.started {
|
||||
return nil, errors.New("HigressOperator is not running")
|
||||
}
|
||||
res := make(map[ComponentName]string)
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
manifest, err := component.RenderManifest()
|
||||
func NewInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (Installer, error) {
|
||||
switch profile.Global.Install {
|
||||
case helm.InstallK8s, helm.InstallLocalK8s:
|
||||
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
|
||||
return nil, fmt.Errorf("failed to build kubernetes client: %w", err)
|
||||
}
|
||||
res[name] = manifest
|
||||
installer, err := NewK8sInstaller(profile, cliClient, writer, quiet)
|
||||
return installer, err
|
||||
case helm.InstallLocalDocker:
|
||||
installer, err := NewDockerInstaller(profile, writer, quiet)
|
||||
return installer, err
|
||||
default:
|
||||
return nil, errors.New("install is not supported")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GenerateManifests generates component manifests to k8s cluster
|
||||
func (o *Installer) GenerateManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into Installer")
|
||||
func GetHomeDir() (string, error) {
|
||||
home := homedir.HomeDir()
|
||||
if home == "" {
|
||||
return "", fmt.Errorf("No user home environment variable found for OS %s", runtime.GOOS)
|
||||
}
|
||||
for _, manifest := range manifestMap {
|
||||
fmt.Fprint(o.writer, manifest)
|
||||
}
|
||||
return nil
|
||||
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// ApplyManifests apply component manifests to k8s cluster
|
||||
func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into Installer")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.applyManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Installer) applyManifest(manifest string, ns string) error {
|
||||
if err := o.kubeCli.CreateNamespace(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
func GetHgctlPath() (string, error) {
|
||||
home, err := GetHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
|
||||
hgctlPath := filepath.Join(home, HgctlHomeDirPath)
|
||||
if _, err := os.Stat(hgctlPath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(hgctlPath, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return hgctlPath, nil
|
||||
}
|
||||
|
||||
// DeleteManifests delete component manifests to k8s cluster
|
||||
func (o *Installer) DeleteManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into Installer")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.deleteManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteManifest delete manifest to certain namespace
|
||||
func (o *Installer) deleteManifest(manifest string, ns string) error {
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
func GetDefaultInstallPackagePath() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
|
||||
path := filepath.Join(dir, StandaloneInstalledPath)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return path, err
|
||||
}
|
||||
|
||||
func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool {
|
||||
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*Installer, error) {
|
||||
if profile == nil {
|
||||
return nil, errors.New("install profile is empty")
|
||||
}
|
||||
// initialize components
|
||||
components := make(map[ComponentName]Component)
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(profile.Global.Namespace),
|
||||
WithComponentChartPath(profile.InstallPackagePath),
|
||||
WithComponentVersion(profile.Charts.Higress.Version),
|
||||
WithComponentRepoURL(profile.Charts.Higress.Url),
|
||||
WithComponentChartName(profile.Charts.Higress.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
higressComponent, err := NewHigressComponent(profile, writer, opts...)
|
||||
func GetProfileInstalledPath() (string, error) {
|
||||
hgctlPath, err := GetHgctlPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
|
||||
return "", err
|
||||
}
|
||||
components[Higress] = higressComponent
|
||||
|
||||
if profile.IstioEnabled() {
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(profile.Global.IstioNamespace),
|
||||
WithComponentChartPath(profile.InstallPackagePath),
|
||||
WithComponentVersion(profile.Charts.Istio.Version),
|
||||
WithComponentRepoURL(profile.Charts.Istio.Url),
|
||||
WithComponentChartName(profile.Charts.Istio.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
profilesPath := filepath.Join(hgctlPath, ProfileInstalledPath)
|
||||
if _, err := os.Stat(profilesPath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(profilesPath, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
|
||||
}
|
||||
components[Istio] = istioCRDComponent
|
||||
}
|
||||
op := &Installer{
|
||||
profile: profile,
|
||||
components: components,
|
||||
kubeCli: cli,
|
||||
writer: writer,
|
||||
}
|
||||
return op, nil
|
||||
return profilesPath, nil
|
||||
}
|
||||
|
||||
func GetInstalledYamlPath() (string, bool) {
|
||||
profileInstalledPath, err := GetProfileInstalledPath()
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
installedYamlFile := filepath.Join(profileInstalledPath, InstalledYamlFileName)
|
||||
if _, err := os.Stat(installedYamlFile); os.IsNotExist(err) {
|
||||
return installedYamlFile, false
|
||||
}
|
||||
return installedYamlFile, true
|
||||
}
|
||||
|
||||
101
pkg/cmd/hgctl/installer/installer_docker.go
Normal file
101
pkg/cmd/hgctl/installer/installer_docker.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type DockerInstaller struct {
|
||||
started bool
|
||||
standalone *StandaloneComponent
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (d *DockerInstaller) Install() error {
|
||||
fmt.Fprintf(d.writer, "\n⌛️ Processing installation... \n\n")
|
||||
|
||||
if err := d.standalone.Install(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(d.writer, "\n✔️ Wrote Profile: \"%s\" \n", profileName)
|
||||
if err := util.WriteFileString(profileName, util.ToYAML(d.profile), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(d.writer, "\n🎊 Install All Resources Complete!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerInstaller) UnInstall() error {
|
||||
|
||||
fmt.Fprintf(d.writer, "\n⌛️ Processing uninstallation... \n\n")
|
||||
|
||||
if err := d.standalone.UnInstall(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(d.writer, "\n✔️ Removed Profile: \"%s\" \n", profileName)
|
||||
os.Remove(profileName)
|
||||
|
||||
fmt.Fprintf(d.writer, "\n🎊 Uninstall All Resources Complete!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerInstaller) Upgrade() error {
|
||||
fmt.Fprintf(d.writer, "\n⌛️ Processing upgrade... \n\n")
|
||||
|
||||
if err := d.standalone.Upgrade(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(d.writer, "\n🎊 Install All Resources Complete!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDockerInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (*DockerInstaller, error) {
|
||||
if profile == nil {
|
||||
return nil, errors.New("install profile is empty")
|
||||
}
|
||||
// initialize components
|
||||
opts := []ComponentOption{
|
||||
WithComponentVersion(profile.Charts.Standalone.Version),
|
||||
WithComponentRepoURL(profile.Charts.Standalone.Url),
|
||||
WithComponentChartName(profile.Charts.Standalone.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
standaloneComponent, err := NewStandaloneComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewStandaloneComponent failed, err: %s", err)
|
||||
}
|
||||
|
||||
op := &DockerInstaller{
|
||||
profile: profile,
|
||||
standalone: standaloneComponent,
|
||||
writer: writer,
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
299
pkg/cmd/hgctl/installer/installer_k8s.go
Normal file
299
pkg/cmd/hgctl/installer/installer_k8s.go
Normal file
@@ -0,0 +1,299 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||
)
|
||||
|
||||
type K8sInstaller struct {
|
||||
started bool
|
||||
components map[ComponentName]Component
|
||||
kubeCli kubernetes.CLIClient
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) Install() error {
|
||||
if _, err := GetProfileInstalledPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := o.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestMap, err := o.RenderManifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.writer, "\n⌛️ Processing installation... \n\n")
|
||||
if err := o.ApplyManifests(manifestMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(o.writer, "\n✔️ Wrote Profile: \"%s\" \n", profileName)
|
||||
if err := util.WriteFileString(profileName, util.ToYAML(o.profile), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.writer, "\n🎊 Install All Resources Complete!\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) UnInstall() error {
|
||||
if _, err := GetProfileInstalledPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := o.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestMap, err := o.RenderManifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.writer, "\n⌛️ Processing uninstallation... \n\n")
|
||||
if err := o.DeleteManifests(manifestMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(o.writer, "\n✔️ Removed Profile: \"%s\" \n", profileName)
|
||||
os.Remove(profileName)
|
||||
|
||||
fmt.Fprintf(o.writer, "\n🎊 Uninstall All Resources Complete!\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) Upgrade() error {
|
||||
return o.Install()
|
||||
}
|
||||
|
||||
// Run must be invoked before invoking other functions.
|
||||
func (o *K8sInstaller) Run() error {
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
if err := component.Run(); err != nil {
|
||||
return fmt.Errorf("component %s run failed, err: %s", name, err)
|
||||
}
|
||||
}
|
||||
o.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderManifests renders component manifests specified by profile.
|
||||
func (o *K8sInstaller) RenderManifests() (map[ComponentName]string, error) {
|
||||
if !o.started {
|
||||
return nil, errors.New("higress installer is not running")
|
||||
}
|
||||
res := make(map[ComponentName]string)
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
manifest, err := component.RenderManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
|
||||
}
|
||||
res[name] = manifest
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GenerateManifests generates component manifests to k8s cluster
|
||||
func (o *K8sInstaller) GenerateManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into K8sInstaller")
|
||||
}
|
||||
for _, manifest := range manifestMap {
|
||||
fmt.Fprint(o.writer, manifest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyManifests apply component manifests to k8s cluster
|
||||
func (o *K8sInstaller) ApplyManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into K8sInstaller")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.applyManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) applyManifest(manifest string, ns string) error {
|
||||
if err := o.kubeCli.CreateNamespace(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteManifests delete component manifests to k8s cluster
|
||||
func (o *K8sInstaller) DeleteManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into K8sInstaller")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.deleteManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteManifest delete manifest to certain namespace
|
||||
func (o *K8sInstaller) deleteManifest(manifest string, ns string) error {
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) isNamespacedObject(obj *object.K8sObject) bool {
|
||||
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewK8sInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*K8sInstaller, error) {
|
||||
if profile == nil {
|
||||
return nil, errors.New("install profile is empty")
|
||||
}
|
||||
// initialize components
|
||||
components := make(map[ComponentName]Component)
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(profile.Global.Namespace),
|
||||
WithComponentChartPath(profile.InstallPackagePath),
|
||||
WithComponentVersion(profile.Charts.Higress.Version),
|
||||
WithComponentRepoURL(profile.Charts.Higress.Url),
|
||||
WithComponentChartName(profile.Charts.Higress.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
higressComponent, err := NewHigressComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
|
||||
}
|
||||
components[Higress] = higressComponent
|
||||
|
||||
if profile.IstioEnabled() {
|
||||
istioNamespace := profile.GetIstioNamespace()
|
||||
if len(istioNamespace) == 0 {
|
||||
istioNamespace = DefaultIstioNamespace
|
||||
}
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(istioNamespace),
|
||||
WithComponentVersion("1.18.2"),
|
||||
WithComponentRepoURL("embed://istiobase"),
|
||||
WithComponentChartName("istio"),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
|
||||
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
|
||||
}
|
||||
components[Istio] = istioCRDComponent
|
||||
}
|
||||
|
||||
if profile.GatewayAPIEnabled() {
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(DefaultGatewayAPINamespace),
|
||||
WithComponentVersion("1.0.0"),
|
||||
WithComponentRepoURL("embed://gatewayapi"),
|
||||
WithComponentChartName("gatewayAPI"),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
|
||||
gatewayAPIComponent, err := NewGatewayAPIComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewGatewayAPIComponent failed, err: %s", err)
|
||||
}
|
||||
components[GatewayAPI] = gatewayAPIComponent
|
||||
}
|
||||
|
||||
op := &K8sInstaller{
|
||||
profile: profile,
|
||||
components: components,
|
||||
kubeCli: cli,
|
||||
writer: writer,
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
@@ -17,9 +17,10 @@ package installer
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,23 +43,27 @@ func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...Compo
|
||||
|
||||
var renderer helm.Renderer
|
||||
var err error
|
||||
if newOpts.RepoURL != "" {
|
||||
renderer, err = helm.NewRemoteRenderer(
|
||||
|
||||
// Istio can be installed by embed type or remote type
|
||||
if strings.HasPrefix(newOpts.RepoURL, "embed://") {
|
||||
chartDir := strings.TrimPrefix(newOpts.RepoURL, "embed://")
|
||||
renderer, err = helm.NewLocalChartRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(manifests.BuiltinOrDir("")),
|
||||
helm.WithDir(chartDir),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
renderer, err = helm.NewLocalRenderer(
|
||||
renderer, err = helm.NewRemoteRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
||||
helm.WithDir(string(Istio)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
139
pkg/cmd/hgctl/installer/standalone.go
Normal file
139
pkg/cmd/hgctl/installer/standalone.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHttpRequestTimeout = 15 * time.Second
|
||||
defaultHttpMaxTry = 3
|
||||
defaultHttpBufferSize = 1024 * 1024 * 2
|
||||
)
|
||||
|
||||
type StandaloneComponent struct {
|
||||
profile *helm.Profile
|
||||
started bool
|
||||
opts *ComponentOptions
|
||||
writer io.Writer
|
||||
httpFetcher *util.HTTPFetcher
|
||||
agent *Agent
|
||||
}
|
||||
|
||||
func (s *StandaloneComponent) Install() error {
|
||||
if !s.opts.Quiet {
|
||||
fmt.Fprintf(s.writer, "\n🏄 Downloading installer from %s\n", s.opts.RepoURL)
|
||||
}
|
||||
// download get-higress.sh
|
||||
data, err := s.httpFetcher.Fetch(context.Background(), s.opts.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// write installer binary shell
|
||||
if err := util.WriteFileString(s.agent.installBinaryName, string(data), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
// start to install higress
|
||||
if err := s.agent.Install(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StandaloneComponent) UnInstall() error {
|
||||
if err := s.agent.Uninstall(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StandaloneComponent) Upgrade() error {
|
||||
if !s.opts.Quiet {
|
||||
fmt.Fprintf(s.writer, "\n🏄 Downloading installer from %s\n", s.opts.RepoURL)
|
||||
}
|
||||
// download get-higress.sh
|
||||
data, err := s.httpFetcher.Fetch(context.Background(), s.opts.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// write installer binary shell
|
||||
if err := util.WriteFileString(s.agent.installBinaryName, string(data), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
// start to upgrade higress
|
||||
if err := s.agent.Upgrade(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewStandaloneComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (*StandaloneComponent, error) {
|
||||
newOpts := &ComponentOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(newOpts)
|
||||
}
|
||||
|
||||
httpFetcher := util.NewHTTPFetcher(defaultHttpRequestTimeout, defaultHttpMaxTry, defaultHttpBufferSize)
|
||||
if err := prepareProfile(profile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agent := NewAgent(profile, writer, newOpts.Quiet)
|
||||
standaloneComponent := &StandaloneComponent{
|
||||
profile: profile,
|
||||
opts: newOpts,
|
||||
writer: writer,
|
||||
httpFetcher: httpFetcher,
|
||||
agent: agent,
|
||||
}
|
||||
return standaloneComponent, nil
|
||||
}
|
||||
|
||||
func prepareProfile(profile *helm.Profile) error {
|
||||
if _, err := GetProfileInstalledPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(profile.InstallPackagePath) == 0 {
|
||||
dir, err := GetDefaultInstallPackagePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
profile.InstallPackagePath = dir
|
||||
}
|
||||
|
||||
if _, err := os.Stat(profile.InstallPackagePath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(profile.InstallPackagePath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// parse INSTALLPACKAGEPATH in storage.url
|
||||
if strings.HasPrefix(profile.Storage.Url, "file://") {
|
||||
profile.Storage.Url = strings.ReplaceAll(profile.Storage.Url, "${INSTALLPACKAGEPATH}", profile.InstallPackagePath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
357
pkg/cmd/hgctl/installer/standalone_agent.go
Normal file
357
pkg/cmd/hgctl/installer/standalone_agent.go
Normal file
@@ -0,0 +1,357 @@
|
||||
// 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/pkg/cmd/hgctl/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-password=%s", a.profile.Console.AdminPasswordValue),
|
||||
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 execeed 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
|
||||
}
|
||||
Reference in New Issue
Block a user