feat:add installation for higress standalone in local docker environment (#606)

Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
This commit is contained in:
Jun
2023-11-02 14:02:23 +08:00
committed by GitHub
parent de1dd3bfbc
commit 9136908354
42 changed files with 21149 additions and 422 deletions

View File

@@ -27,6 +27,7 @@ header:
- 'tools/' - 'tools/'
- 'test/README.md' - 'test/README.md'
- 'pkg/cmd/hgctl/testdata/config' - 'pkg/cmd/hgctl/testdata/config'
- 'pkg/cmd/hgctl/manifests'
comment: on-failure comment: on-failure
dependency: dependency:

3
go.mod
View File

@@ -172,6 +172,8 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.9 // indirect github.com/klauspost/compress v1.15.9 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
@@ -225,6 +227,7 @@ require (
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/statsd_exporter v0.21.0 // indirect github.com/prometheus/statsd_exporter v0.21.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.6.1 // indirect
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
github.com/russross/blackfriday v1.6.0 // indirect github.com/russross/blackfriday v1.6.0 // indirect
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect

View File

@@ -104,7 +104,8 @@ func newDashboardCmd() *cobra.Command {
controllerDebugCmd := controllerDebugCmd() controllerDebugCmd := controllerDebugCmd()
controllerDebugCmd.PersistentFlags().IntVar(&controllerPort, "ui-port", defaultControllerPort, "The component dashboard UI port.") controllerDebugCmd.PersistentFlags().IntVar(&controllerPort, "ui-port", defaultControllerPort, "The component dashboard UI port.")
dashboardCmd.AddCommand(controllerDebugCmd) dashboardCmd.AddCommand(controllerDebugCmd)
flags := dashboardCmd.PersistentFlags()
options.AddKubeConfigFlags(flags)
return dashboardCmd return dashboardCmd
} }

View File

@@ -26,15 +26,40 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
// ReadYamlProfile gets the overlay yaml file from list of files and return profile value from file overlay and set overlay. // GetProfileFromFlags get profile name from flags.
func ReadYamlProfile(inFilenames []string, setFlags []string) (string, string, error) { func GetProfileFromFlags(setFlags []string) (string, error) {
profileName := DefaultProfileName profileName := DefaultProfileName
// The profile coming from --set flag has the highest precedence. // The profile coming from --set flag has the highest precedence.
psf := GetValueForSetFlag(setFlags, "profile") psf := GetValueForSetFlag(setFlags, "profile")
if psf != "" { if psf != "" {
profileName = psf profileName = psf
} }
return "", profileName, nil return profileName, nil
}
func GetValuesOverylayFromFiles(inFilenames []string) (string, error) {
// Convert layeredYamls under values node in profile file to support helm values
overLayYamls := ""
// Get Overlays from files
if len(inFilenames) > 0 {
layeredYamls, err := ReadLayeredYAMLs(inFilenames)
if err != nil {
return "", err
}
vals := make(map[string]any)
if err := yaml.Unmarshal([]byte(layeredYamls), &vals); err != nil {
return "", fmt.Errorf("%s:\n\nYAML:\n%s", err, layeredYamls)
}
values := make(map[string]any)
values["values"] = vals
out, err := yaml.Marshal(values)
if err != nil {
return "", err
}
overLayYamls = string(out)
}
return overLayYamls, nil
} }
func GetUninstallProfileName() string { func GetUninstallProfileName() string {
@@ -101,12 +126,17 @@ func GenerateConfig(inFilenames []string, setFlags []string) (string, *Profile,
return "", nil, "", err return "", nil, "", err
} }
fy, profileName, err := ReadYamlProfile(inFilenames, setFlags) profileName, err := GetProfileFromFlags(setFlags)
if err != nil { if err != nil {
return "", nil, "", err return "", nil, "", err
} }
profileString, profile, err := GenProfile(profileName, fy, setFlags) valuesOverlay, err := GetValuesOverylayFromFiles(inFilenames)
if err != nil {
return "", nil, "", err
}
profileString, profile, err := GenProfile(profileName, valuesOverlay, setFlags)
if err != nil { if err != nil {
return "", nil, "", err return "", nil, "", err

View File

@@ -17,7 +17,9 @@ package helm
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
"istio.io/istio/operator/pkg/util"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
@@ -45,42 +47,37 @@ type Profile struct {
type ProfileGlobal struct { type ProfileGlobal struct {
Install InstallMode `json:"install,omitempty"` Install InstallMode `json:"install,omitempty"`
IngressClass string `json:"ingressClass,omitempty"` IngressClass string `json:"ingressClass,omitempty"`
WatchNamespace string `json:"watchNamespace,omitempty"`
DisableAlpnH2 bool `json:"disableAlpnH2,omitempty"`
EnableStatus bool `json:"enableStatus,omitempty"`
EnableIstioAPI bool `json:"enableIstioAPI,omitempty"` EnableIstioAPI bool `json:"enableIstioAPI,omitempty"`
EnableGatewayAPI bool `json:"enableGatewayAPI,omitempty"`
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
IstioNamespace string `json:"istioNamespace,omitempty"`
} }
func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) { func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0) sets := make([]string, 0)
if install == InstallK8s || install == InstallLocalK8s {
sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass)) sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass))
sets = append(sets, fmt.Sprintf("global.watchNamespace=%s", p.WatchNamespace))
sets = append(sets, fmt.Sprintf("global.disableAlpnH2=%t", p.DisableAlpnH2))
sets = append(sets, fmt.Sprintf("global.enableStatus=%t", p.EnableStatus))
sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI)) sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI))
sets = append(sets, fmt.Sprintf("global.istioNamespace=%s", p.IstioNamespace)) sets = append(sets, fmt.Sprintf("global.enableGatewayAPI=%t", p.EnableGatewayAPI))
if install == InstallLocalK8s { if install == InstallLocalK8s {
sets = append(sets, fmt.Sprintf("global.local=%t", true)) sets = append(sets, fmt.Sprintf("global.local=%t", true))
} }
}
return sets, nil return sets, nil
} }
func (p ProfileGlobal) Validate(install InstallMode) []error { func (p ProfileGlobal) Validate(install InstallMode) []error {
errs := make([]error, 0) errs := make([]error, 0)
// now only support k8s and local-k8s installation mode // now only support k8s, local-k8s, local-docker installation mode
if p.Install != InstallK8s && p.Install != InstallLocalK8s { if install != InstallK8s && install != InstallLocalK8s && install != InstallLocalDocker {
errs = append(errs, errors.New("global.install only can be set to k8s or local-k8s")) errs = append(errs, errors.New("global.install only can be set to k8s, local-k8s or local-docker"))
} }
if install == InstallK8s || install == InstallLocalK8s {
if len(p.IngressClass) == 0 { if len(p.IngressClass) == 0 {
errs = append(errs, errors.New("global.ingressClass can't be empty")) errs = append(errs, errors.New("global.ingressClass can't be empty"))
} }
if len(p.Namespace) == 0 { if len(p.Namespace) == 0 {
errs = append(errs, errors.New("global.namespace can't be empty")) errs = append(errs, errors.New("global.namespace can't be empty"))
} }
if len(p.IstioNamespace) == 0 {
errs = append(errs, errors.New("global.istioNamespace can't be empty"))
} }
return errs return errs
} }
@@ -88,38 +85,32 @@ func (p ProfileGlobal) Validate(install InstallMode) []error {
type ProfileConsole struct { type ProfileConsole struct {
Port uint32 `json:"port,omitempty"` Port uint32 `json:"port,omitempty"`
Replicas uint32 `json:"replicas,omitempty"` Replicas uint32 `json:"replicas,omitempty"`
ServiceType string `json:"serviceType,omitempty"`
Domain string `json:"domain,omitempty"`
TlsSecretName string `json:"tlsSecretName,omitempty"`
WebLoginPrompt string `json:"webLoginPrompt,omitempty"`
AdminPasswordValue string `json:"adminPasswordValue,omitempty"` AdminPasswordValue string `json:"adminPasswordValue,omitempty"`
AdminPasswordLength uint32 `json:"adminPasswordLength,omitempty"`
O11yEnabled bool `json:"o11YEnabled,omitempty"` O11yEnabled bool `json:"o11YEnabled,omitempty"`
PvcRwxSupported bool `json:"pvcRwxSupported,omitempty"`
} }
func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) { func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0) sets := make([]string, 0)
if install == InstallK8s || install == InstallLocalK8s {
sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas)) sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas))
sets = append(sets, fmt.Sprintf("higress-console.service.type=%s", p.ServiceType))
sets = append(sets, fmt.Sprintf("higress-console.domain=%s", p.Domain))
sets = append(sets, fmt.Sprintf("higress-console.tlsSecretName=%s", p.TlsSecretName))
sets = append(sets, fmt.Sprintf("higress-console.web.login.prompt=%s", p.WebLoginPrompt))
sets = append(sets, fmt.Sprintf("higress-console.admin.password.value=%s", p.AdminPasswordValue)) sets = append(sets, fmt.Sprintf("higress-console.admin.password.value=%s", p.AdminPasswordValue))
sets = append(sets, fmt.Sprintf("higress-console.admin.password.length=%d", p.AdminPasswordLength))
sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled)) sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled))
sets = append(sets, fmt.Sprintf("higress-console.pvc.rwxSupported=%t", p.PvcRwxSupported)) }
return sets, nil return sets, nil
} }
func (p ProfileConsole) Validate(install InstallMode) []error { func (p ProfileConsole) Validate(install InstallMode) []error {
errs := make([]error, 0) errs := make([]error, 0)
if install == InstallK8s || install == InstallLocalK8s {
if p.Replicas <= 0 { if p.Replicas <= 0 {
errs = append(errs, errors.New("console.replica need be large than zero")) errs = append(errs, errors.New("console.replica need be large than zero"))
} }
}
if p.ServiceType != "ClusterIP" && p.ServiceType != "NodePort" && p.ServiceType != "LoadBalancer" { if install == InstallLocalDocker {
errs = append(errs, errors.New("console.serviceType can only be set to ClusterIP, NodePort or LoadBalancer")) if p.Port <= 0 {
errs = append(errs, errors.New("console.port need be large than zero"))
}
} }
return errs return errs
@@ -134,16 +125,31 @@ type ProfileGateway struct {
func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) { func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0) sets := make([]string, 0)
if install == InstallK8s || install == InstallLocalK8s {
sets = append(sets, fmt.Sprintf("higress-core.gateway.replicas=%d", p.Replicas)) sets = append(sets, fmt.Sprintf("higress-core.gateway.replicas=%d", p.Replicas))
}
return sets, nil return sets, nil
} }
func (p ProfileGateway) Validate(install InstallMode) []error { func (p ProfileGateway) Validate(install InstallMode) []error {
errs := make([]error, 0) errs := make([]error, 0)
if install == InstallK8s || install == InstallLocalK8s {
if p.Replicas <= 0 { if p.Replicas <= 0 {
errs = append(errs, errors.New("gateway.replica need be large than zero")) errs = append(errs, errors.New("gateway.replica need be large than zero"))
} }
}
if install == InstallLocalDocker {
if p.HttpPort <= 0 {
errs = append(errs, errors.New("gateway.httpPort need be large than zero"))
}
if p.HttpsPort <= 0 {
errs = append(errs, errors.New("gateway.httpsPort need be large than zero"))
}
if p.MetricsPort <= 0 {
errs = append(errs, errors.New("gateway.MetricsPort need be large than zero"))
}
}
return errs return errs
} }
@@ -153,16 +159,19 @@ type ProfileController struct {
func (p ProfileController) SetFlags(install InstallMode) ([]string, error) { func (p ProfileController) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0) sets := make([]string, 0)
if install == InstallK8s || install == InstallLocalK8s {
sets = append(sets, fmt.Sprintf("higress-core.controller.replicas=%d", p.Replicas)) sets = append(sets, fmt.Sprintf("higress-core.controller.replicas=%d", p.Replicas))
}
return sets, nil return sets, nil
} }
func (p ProfileController) Validate(install InstallMode) []error { func (p ProfileController) Validate(install InstallMode) []error {
errs := make([]error, 0) errs := make([]error, 0)
if install == InstallK8s || install == InstallLocalK8s {
if p.Replicas <= 0 { if p.Replicas <= 0 {
errs = append(errs, errors.New("controller.replica need be large than zero")) errs = append(errs, errors.New("controller.replica need be large than zero"))
} }
}
return errs return errs
} }
@@ -176,6 +185,31 @@ type ProfileStorage struct {
func (p ProfileStorage) Validate(install InstallMode) []error { func (p ProfileStorage) Validate(install InstallMode) []error {
errs := make([]error, 0) errs := make([]error, 0)
if install == InstallLocalDocker {
if len(p.Url) == 0 {
errs = append(errs, errors.New("storage.url can't be empty"))
}
if len(p.Ns) == 0 {
errs = append(errs, errors.New("storage.ns can't be empty"))
}
if !strings.HasPrefix(p.Url, "nacos://") && !strings.HasPrefix(p.Url, "file://") {
errs = append(errs, fmt.Errorf("invalid storage url: %s", p.Url))
} else {
// check localhost or 127.0.0.0
if strings.Contains(p.Url, "localhost") || strings.Contains(p.Url, "/127.") {
errs = append(errs, errors.New("localhost or loopback addresses in nacos url won't work"))
}
}
if len(p.DataEncKey) > 0 && len(p.DataEncKey) != 32 {
errs = append(errs, fmt.Errorf("expecting 32 characters for dataEncKey, but got %d length", len(p.DataEncKey)))
}
if len(p.Username) > 0 && len(p.Password) == 0 || len(p.Username) == 0 && len(p.Password) > 0 {
errs = append(errs, errors.New("both nacos username and password should be provided"))
}
}
return errs return errs
} }
@@ -187,7 +221,7 @@ type Chart struct {
type ProfileCharts struct { type ProfileCharts struct {
Higress Chart `json:"higress,omitempty"` Higress Chart `json:"higress,omitempty"`
Istio Chart `json:"istio,omitempty"` Standalone Chart `json:"standalone,omitempty"`
} }
func (p ProfileCharts) Validate(install InstallMode) []error { func (p ProfileCharts) Validate(install InstallMode) []error {
@@ -222,8 +256,13 @@ func (p *Profile) ValuesYaml() (string, error) {
} }
valueOverlayYAML = string(out) valueOverlayYAML = string(out)
} }
flagsYAML, err := overlaySetFlagValues("", setFlags)
if err != nil {
return "", err
}
// merge values and setFlags // merge values and setFlags
overlayYAML, err := overlaySetFlagValues(valueOverlayYAML, setFlags) overlayYAML, err := util.OverlayYAML(flagsYAML, valueOverlayYAML)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -237,6 +276,26 @@ func (p *Profile) IstioEnabled() bool {
return false return false
} }
func (p *Profile) GatewayAPIEnabled() bool {
if (p.Global.Install == InstallK8s || p.Global.Install == InstallLocalK8s) && p.Global.EnableGatewayAPI {
return true
}
return false
}
func (p *Profile) GetIstioNamespace() string {
if valuesGlobal, ok1 := p.Values["global"]; ok1 {
if global, ok2 := valuesGlobal.(map[string]any); ok2 {
if istioNamespace, ok3 := global["istioNamespace"]; ok3 {
if namespace, ok4 := istioNamespace.(string); ok4 {
return namespace
}
}
}
}
return ""
}
func (p *Profile) Validate() error { func (p *Profile) Validate() error {
errs := make([]error, 0) errs := make([]error, 0)
errsGlobal := p.Global.Validate(p.Global.Install) errsGlobal := p.Global.Validate(p.Global.Install)
@@ -256,7 +315,7 @@ func (p *Profile) Validate() error {
errs = append(errs, errsController...) errs = append(errs, errsController...)
} }
errsStorage := p.Storage.Validate(p.Global.Install) errsStorage := p.Storage.Validate(p.Global.Install)
if len(errsController) > 0 { if len(errsStorage) > 0 {
errs = append(errs, errsStorage...) errs = append(errs, errsStorage...)
} }
errsCharts := p.Charts.Validate(p.Global.Install) errsCharts := p.Charts.Validate(p.Global.Install)

View File

@@ -127,7 +127,7 @@ type RendererOptions struct {
Name string Name string
Namespace string Namespace string
// fields for LocalRenderer // fields for LocalChartRenderer and LocalFileRenderer
FS fs.FS FS fs.FS
Dir string Dir string
@@ -174,14 +174,84 @@ func WithRepoURL(repo string) RendererOption {
} }
} }
// LocalRenderer load chart from local file system // LocalFileRenderer load yaml files from local file system
type LocalRenderer struct { type LocalFileRenderer struct {
Opts *RendererOptions
filesMap map[string]string
Started bool
}
func NewLocalFileRenderer(opts ...RendererOption) (Renderer, error) {
newOpts := &RendererOptions{}
for _, opt := range opts {
opt(newOpts)
}
return &LocalFileRenderer{
Opts: newOpts,
filesMap: make(map[string]string),
}, nil
}
func (l *LocalFileRenderer) Init() error {
fileNames, err := getFileNames(l.Opts.FS, l.Opts.Dir)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("chart of component %s doesn't exist", l.Opts.Name)
}
return fmt.Errorf("getFileNames err: %s", err)
}
for _, fileName := range fileNames {
data, err := fs.ReadFile(l.Opts.FS, fileName)
if err != nil {
return fmt.Errorf("ReadFile %s err: %s", fileName, err)
}
l.filesMap[fileName] = string(data)
}
l.Started = true
return nil
}
func (l *LocalFileRenderer) RenderManifest(valsYaml string) (string, error) {
if !l.Started {
return "", errors.New("LocalFileRenderer has not been init")
}
keys := make([]string, 0, len(l.filesMap))
for key := range l.filesMap {
keys = append(keys, key)
}
// to ensure that every manifest rendered by same values are the same
sort.Strings(keys)
var builder strings.Builder
for i := 0; i < len(keys); i++ {
file := l.filesMap[keys[i]]
file = util.ApplyFilters(file, DefaultFilters...)
// ignore empty manifest
if file == "" {
continue
}
if !strings.HasSuffix(file, YAMLSeparator) {
file += YAMLSeparator
}
builder.WriteString(file)
}
return builder.String(), nil
}
func (l *LocalFileRenderer) SetVersion(version string) {
l.Opts.Version = version
}
// LocalChartRenderer load chart from local file system
type LocalChartRenderer struct {
Opts *RendererOptions Opts *RendererOptions
Chart *chart.Chart Chart *chart.Chart
Started bool Started bool
} }
func (lr *LocalRenderer) Init() error { func (lr *LocalChartRenderer) Init() error {
fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir) fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -212,18 +282,18 @@ func (lr *LocalRenderer) Init() error {
return nil return nil
} }
func (lr *LocalRenderer) RenderManifest(valsYaml string) (string, error) { func (lr *LocalChartRenderer) RenderManifest(valsYaml string) (string, error) {
if !lr.Started { if !lr.Started {
return "", errors.New("LocalRenderer has not been init") return "", errors.New("LocalChartRenderer has not been init")
} }
return renderManifest(valsYaml, lr.Chart, true, lr.Opts, DefaultFilters...) return renderManifest(valsYaml, lr.Chart, true, lr.Opts, DefaultFilters...)
} }
func (lr *LocalRenderer) SetVersion(version string) { func (lr *LocalChartRenderer) SetVersion(version string) {
lr.Opts.Version = version lr.Opts.Version = version
} }
func NewLocalRenderer(opts ...RendererOption) (Renderer, error) { func NewLocalChartRenderer(opts ...RendererOption) (Renderer, error) {
newOpts := &RendererOptions{} newOpts := &RendererOptions{}
for _, opt := range opts { for _, opt := range opts {
opt(newOpts) opt(newOpts)
@@ -232,7 +302,7 @@ func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
if err := verifyRendererOptions(newOpts); err != nil { if err := verifyRendererOptions(newOpts); err != nil {
return nil, fmt.Errorf("verify err: %s", err) return nil, fmt.Errorf("verify err: %s", err)
} }
return &LocalRenderer{ return &LocalChartRenderer{
Opts: newOpts, Opts: newOpts,
}, nil }, nil
} }

View File

@@ -21,7 +21,6 @@ import (
"github.com/alibaba/higress/pkg/cmd/hgctl/helm" "github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer" "github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options" "github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -32,13 +31,16 @@ const (
// manifestsFlagHelpStr is the command line description for --manifests // manifestsFlagHelpStr is the command line description for --manifests
manifestsFlagHelpStr = `Specify a path to a directory of profiles manifestsFlagHelpStr = `Specify a path to a directory of profiles
(e.g. ~/Downloads/higress/manifests).` (e.g. ~/Downloads/higress/manifests).`
filenameFlagHelpStr = "Path to file containing helm custom values"
outputHelpstr = "Specify a file to write profile yaml" outputHelpstr = "Specify a file to write profile yaml"
profileNameK8s = "k8s" profileNameK8s = "k8s"
profileNameLocalK8s = "local-k8s" profileNameLocalK8s = "local-k8s"
profileNameLocalDocker = "local-docker"
) )
type InstallArgs struct { type InstallArgs struct {
// InFilenames is a filename to helm custom values
InFilenames []string InFilenames []string
// KubeConfigPath is the path to kube config file. // KubeConfigPath is the path to kube config file.
KubeConfigPath string KubeConfigPath string
@@ -61,6 +63,7 @@ func (a *InstallArgs) String() string {
} }
func addInstallFlags(cmd *cobra.Command, args *InstallArgs) { func addInstallFlags(cmd *cobra.Command, args *InstallArgs) {
cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr) cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr) cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
} }
@@ -87,19 +90,23 @@ func newInstallCmd() *cobra.Command {
# Install higress on local kubernetes cluster # Install higress on local kubernetes cluster
hgctl install --set profile=local-k8s hgctl install --set profile=local-k8s
# Install higress on local docker environment with specific gateway port
hgctl install --set profile=local-docker --set gateway.httpPort=80 --set gateway.httpsPort=443
# To override profile setting # To override profile setting
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2" hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
# To override helm setting # To override helm setting
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m" hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m"
`, `,
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
return nil return nil
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return Install(cmd.OutOrStdout(), iArgs) return install(cmd.OutOrStdout(), iArgs)
}, },
} }
addInstallFlags(installCmd, iArgs) addInstallFlags(installCmd, iArgs)
@@ -108,7 +115,7 @@ func newInstallCmd() *cobra.Command {
return installCmd return installCmd
} }
func Install(writer io.Writer, iArgs *InstallArgs) error { func install(writer io.Writer, iArgs *InstallArgs) error {
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath) setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
// check profileName // check profileName
@@ -133,7 +140,7 @@ func Install(writer io.Writer, iArgs *InstallArgs) error {
return err return err
} }
err = InstallManifests(profile, writer) err = installManifests(profile, writer)
if err != nil { if err != nil {
return fmt.Errorf("failed to install manifests: %v", err) return fmt.Errorf("failed to install manifests: %v", err)
} }
@@ -158,11 +165,12 @@ func promptInstall(writer io.Writer, profileName string) bool {
func promptProfileName(writer io.Writer) string { func promptProfileName(writer io.Writer) string {
answer := "" answer := ""
fmt.Fprintf(writer, "Please select higress install configration profile:\n") fmt.Fprintf(writer, "\nPlease select higress install configration profile:\n")
fmt.Fprintf(writer, "1.Install higress to local kubernetes cluster like kind etc.\n") fmt.Fprintf(writer, "\n1.Install higress to local kubernetes cluster like kind etc.\n")
fmt.Fprintf(writer, "2.Install higress to kubernetes cluster\n") fmt.Fprintf(writer, "\n2.Install higress to kubernetes cluster\n")
fmt.Fprintf(writer, "\n3.Install higress to local docker environment\n")
for { for {
fmt.Fprintf(writer, "Please input 1 or 2 to select, input your selection:") fmt.Fprintf(writer, "\nPlease input 1, 2 or 3 to select, input your selection:")
fmt.Scanln(&answer) fmt.Scanln(&answer)
if strings.TrimSpace(answer) == "1" { if strings.TrimSpace(answer) == "1" {
return profileNameLocalK8s return profileNameLocalK8s
@@ -170,33 +178,23 @@ func promptProfileName(writer io.Writer) string {
if strings.TrimSpace(answer) == "2" { if strings.TrimSpace(answer) == "2" {
return profileNameK8s return profileNameK8s
} }
if strings.TrimSpace(answer) == "3" {
return profileNameLocalDocker
}
} }
} }
func InstallManifests(profile *helm.Profile, writer io.Writer) error { func installManifests(profile *helm.Profile, writer io.Writer) error {
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader()) installer, err := installer.NewInstaller(profile, writer, false)
if err != nil {
return fmt.Errorf("failed to build kubernetes client: %w", err)
}
op, err := installer.NewInstaller(profile, cliClient, writer, false)
if err != nil {
return err
}
if err := op.Run(); err != nil {
return err
}
manifestMap, err := op.RenderManifests()
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(writer, "\n⌛ Processing installation... \n\n") err = installer.Install()
if err := op.ApplyManifests(manifestMap); err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(writer, "\n🎊 Install All Resources Complete!\n")
return nil return nil
} }

View 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
}

View File

@@ -15,11 +15,10 @@
package installer package installer
import ( import (
"errors"
"fmt" "fmt"
"io"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm" "github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"io"
) )
const ( const (
@@ -96,10 +95,12 @@ func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...Compon
opt(newOpts) opt(newOpts)
} }
var renderer helm.Renderer if len(newOpts.RepoURL) == 0 {
var err error return nil, errors.New("Higress helm chart url can't be empty")
if newOpts.RepoURL != "" { }
renderer, err = helm.NewRemoteRenderer(
// Higress can only be installed by remote type
renderer, err := helm.NewRemoteRenderer(
helm.WithName(newOpts.ChartName), helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace), helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL), helm.WithRepoURL(newOpts.RepoURL),
@@ -108,18 +109,6 @@ func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...Compon
if err != nil { if err != nil {
return nil, err 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
}
}
higressComponent := &HigressComponent{ higressComponent := &HigressComponent{
profile: profile, profile: profile,

View File

@@ -18,196 +18,113 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath"
"runtime"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm" "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/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"k8s.io/client-go/util/homedir"
) )
type Installer struct { const (
started bool HgctlHomeDirPath = ".hgctl"
components map[ComponentName]Component StandaloneInstalledPath = "higress-standalone"
kubeCli kubernetes.CLIClient ProfileInstalledPath = "profiles"
profile *helm.Profile InstalledYamlFileName = "install.yaml"
writer io.Writer DefaultGatewayAPINamespace = "gateway-system"
DefaultIstioNamespace = "istio-system"
)
type Installer interface {
Install() error
UnInstall() error
Upgrade() error
} }
// Run must be invoked before invoking other functions. func NewInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (Installer, error) {
func (o *Installer) Run() error { switch profile.Global.Install {
for name, component := range o.components { case helm.InstallK8s, helm.InstallLocalK8s:
if !component.Enabled() { cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
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()
if err != nil { 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 GetHomeDir() (string, error) {
func (o *Installer) GenerateManifests(manifestMap map[ComponentName]string) error { home := homedir.HomeDir()
if o.kubeCli == nil { if home == "" {
return errors.New("no injected k8s cli into Installer") return "", fmt.Errorf("No user home environment variable found for OS %s", runtime.GOOS)
} }
for _, manifest := range manifestMap {
fmt.Fprint(o.writer, manifest) return home, nil
}
return nil
} }
// ApplyManifests apply component manifests to k8s cluster func GetHgctlPath() (string, error) {
func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error { home, err := GetHomeDir()
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)
if err != nil { if err != nil {
return err return "", err
} }
for _, obj := range objs {
// check namespaced object if namespace property has been existed hgctlPath := filepath.Join(home, HgctlHomeDirPath)
if obj.Namespace == "" && o.isNamespacedObject(obj) { if _, err := os.Stat(hgctlPath); os.IsNotExist(err) {
obj.Namespace = ns if err = os.MkdirAll(hgctlPath, os.ModePerm); err != nil {
obj.UnstructuredObject().SetNamespace(ns) return "", err
}
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
return hgctlPath, nil
} }
// DeleteManifests delete component manifests to k8s cluster func GetDefaultInstallPackagePath() (string, error) {
func (o *Installer) DeleteManifests(manifestMap map[ComponentName]string) error { dir, err := os.Getwd()
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)
if err != nil { if err != nil {
return err return "", err
} }
for _, obj := range objs {
// check namespaced object if namespace property has been existed path := filepath.Join(dir, StandaloneInstalledPath)
if obj.Namespace == "" && o.isNamespacedObject(obj) { if _, err := os.Stat(path); os.IsNotExist(err) {
obj.Namespace = ns if err = os.MkdirAll(path, os.ModePerm); err != nil {
obj.UnstructuredObject().SetNamespace(ns) return "", err
}
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 return path, err
} }
func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool { func GetProfileInstalledPath() (string, error) {
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" { hgctlPath, err := GetHgctlPath()
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...)
if err != nil { 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())
} }
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...) profilesPath := filepath.Join(hgctlPath, ProfileInstalledPath)
if err != nil { if _, err := os.Stat(profilesPath); os.IsNotExist(err) {
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err) if err = os.MkdirAll(profilesPath, os.ModePerm); err != nil {
return "", err
} }
components[Istio] = istioCRDComponent
} }
op := &Installer{
profile: profile, return profilesPath, nil
components: components, }
kubeCli: cli,
writer: writer, func GetInstalledYamlPath() (string, bool) {
} profileInstalledPath, err := GetProfileInstalledPath()
return op, nil if err != nil {
return "", false
}
installedYamlFile := filepath.Join(profileInstalledPath, InstalledYamlFileName)
if _, err := os.Stat(installedYamlFile); os.IsNotExist(err) {
return installedYamlFile, false
}
return installedYamlFile, true
} }

View 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
}

View 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
}

View File

@@ -17,9 +17,10 @@ package installer
import ( import (
"fmt" "fmt"
"io" "io"
"os" "strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm" "github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
) )
const ( const (
@@ -42,23 +43,27 @@ func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...Compo
var renderer helm.Renderer var renderer helm.Renderer
var err error 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.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace), helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL), helm.WithRepoURL(newOpts.RepoURL),
helm.WithVersion(newOpts.Version), helm.WithVersion(newOpts.Version),
helm.WithFS(manifests.BuiltinOrDir("")),
helm.WithDir(chartDir),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
renderer, err = helm.NewLocalRenderer( renderer, err = helm.NewRemoteRenderer(
helm.WithName(newOpts.ChartName), helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace), helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL),
helm.WithVersion(newOpts.Version), helm.WithVersion(newOpts.Version),
helm.WithFS(os.DirFS(newOpts.ChartPath)),
helm.WithDir(string(Istio)),
) )
if err != nil { if err != nil {
return nil, err return nil, err

View 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
}

View 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
}

View File

@@ -59,12 +59,15 @@ func newManifestCmd() *cobra.Command {
generate := newManifestGenerateCmd(iArgs) generate := newManifestGenerateCmd(iArgs)
addManifestFlags(generate, iArgs) addManifestFlags(generate, iArgs)
flags := generate.Flags()
options.AddKubeConfigFlags(flags)
manifestCmd.AddCommand(generate) manifestCmd.AddCommand(generate)
return manifestCmd return manifestCmd
} }
func addManifestFlags(cmd *cobra.Command, args *ManifestArgs) { func addManifestFlags(cmd *cobra.Command, args *ManifestArgs) {
cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr) cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr) cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
} }
@@ -123,7 +126,7 @@ func genManifests(profile *helm.Profile, writer io.Writer) error {
return fmt.Errorf("failed to build kubernetes client: %w", err) return fmt.Errorf("failed to build kubernetes client: %w", err)
} }
op, err := installer.NewInstaller(profile, cliClient, writer, true) op, err := installer.NewK8sInstaller(profile, cliClient, writer, true)
if err != nil { if err != nil {
return err return err
} }

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
apiVersion: v1
appVersion: 1.18.2
description: Helm chart for deploying Istio cluster resources and CRDs
icon: https://istio.io/latest/favicons/android-192x192.png
keywords:
- istio
name: base
sources:
- https://github.com/istio/istio
version: 1.18.2

View File

@@ -0,0 +1,21 @@
# Istio base Helm Chart
This chart installs resources shared by all Istio revisions. This includes Istio CRDs.
## Setup Repo Info
```console
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
```
_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._
## Installing the Chart
To install the chart with the release name `istio-base`:
```console
kubectl create namespace istio-system
helm install istio-base istio/base -n istio-system
```

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
# SYNC WITH manifests/charts/istio-operator/templates
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: istiooperators.install.istio.io
labels:
release: istio
spec:
conversion:
strategy: None
group: install.istio.io
names:
kind: IstioOperator
listKind: IstioOperatorList
plural: istiooperators
singular: istiooperator
shortNames:
- iop
- io
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Istio control plane revision
jsonPath: .spec.revision
name: Revision
type: string
- description: IOP current state
jsonPath: .status.status
name: Status
type: string
- description: 'CreationTimestamp is a timestamp representing the server time
when this object was created. It is not guaranteed to be set in happens-before
order across separate operations. Clients may not set this value. It is represented
in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for
lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
jsonPath: .metadata.creationTimestamp
name: Age
type: date
subresources:
status: {}
name: v1alpha1
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
served: true
storage: true
---

View File

@@ -0,0 +1,5 @@
Istio base successfully installed!
To learn more about the release, try:
$ helm status {{ .Release.Name }}
$ helm get all {{ .Release.Name }}

View File

@@ -0,0 +1,181 @@
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# DO NOT EDIT!
# THIS IS A LEGACY CHART HERE FOR BACKCOMPAT
# UPDATED CHART AT manifests/charts/istio-control/istio-discovery
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: istiod-{{ .Values.global.istioNamespace }}
labels:
app: istiod
release: {{ .Release.Name }}
rules:
# sidecar injection controller
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
verbs: ["get", "list", "watch", "update", "patch"]
# configuration validation webhook controller
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["validatingwebhookconfigurations"]
verbs: ["get", "list", "watch", "update"]
# istio configuration
# removing CRD permissions can break older versions of Istio running alongside this control plane (https://github.com/istio/istio/issues/29382)
# please proceed with caution
- apiGroups: ["config.istio.io", "security.istio.io", "networking.istio.io", "authentication.istio.io", "rbac.istio.io", "telemetry.istio.io"]
verbs: ["get", "watch", "list"]
resources: ["*"]
{{- if .Values.global.istiod.enableAnalysis }}
- apiGroups: ["config.istio.io", "security.istio.io", "networking.istio.io", "authentication.istio.io", "rbac.istio.io", "telemetry.istio.io"]
verbs: ["update"]
# TODO: should be on just */status but wildcard is not supported
resources: ["*"]
{{- end }}
- apiGroups: ["networking.istio.io"]
verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ]
resources: [ "workloadentries" ]
- apiGroups: ["networking.istio.io"]
verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ]
resources: [ "workloadentries/status" ]
# auto-detect installed CRD definitions
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
# discovery and routing
- apiGroups: [""]
resources: ["pods", "nodes", "services", "namespaces", "endpoints"]
verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
# ingress controller
{{- if .Values.global.istiod.enableAnalysis }}
- apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses/status"]
verbs: ["*"]
{{- end}}
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "ingressclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses/status"]
verbs: ["*"]
# required for CA's namespace controller
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["create", "get", "list", "watch", "update"]
# Istiod and bootstrap.
- apiGroups: ["certificates.k8s.io"]
resources:
- "certificatesigningrequests"
- "certificatesigningrequests/approval"
- "certificatesigningrequests/status"
verbs: ["update", "create", "get", "delete", "watch"]
- apiGroups: ["certificates.k8s.io"]
resources:
- "signers"
resourceNames:
- "kubernetes.io/legacy-unknown"
verbs: ["approve"]
# Used by Istiod to verify the JWT tokens
- apiGroups: ["authentication.k8s.io"]
resources: ["tokenreviews"]
verbs: ["create"]
# Used by Istiod to verify gateway SDS
- apiGroups: ["authorization.k8s.io"]
resources: ["subjectaccessreviews"]
verbs: ["create"]
# Use for Kubernetes Service APIs
- apiGroups: ["networking.x-k8s.io", "gateway.networking.k8s.io"]
resources: ["*"]
verbs: ["get", "watch", "list"]
- apiGroups: ["networking.x-k8s.io", "gateway.networking.k8s.io"]
resources: ["*"] # TODO: should be on just */status but wildcard is not supported
verbs: ["update"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gatewayclasses"]
verbs: ["create", "update", "patch", "delete"]
# Needed for multicluster secret reading, possibly ingress certs in the future
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
# Used for MCS serviceexport management
- apiGroups: ["multicluster.x-k8s.io"]
resources: ["serviceexports"]
verbs: ["get", "watch", "list", "create", "delete"]
# Used for MCS serviceimport management
- apiGroups: ["multicluster.x-k8s.io"]
resources: ["serviceimports"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: istio-reader-{{ .Values.global.istioNamespace }}
labels:
app: istio-reader
release: {{ .Release.Name }}
rules:
- apiGroups:
- "config.istio.io"
- "security.istio.io"
- "networking.istio.io"
- "authentication.istio.io"
- "rbac.istio.io"
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["endpoints", "pods", "services", "nodes", "replicationcontrollers", "namespaces", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.istio.io"]
verbs: [ "get", "watch", "list" ]
resources: [ "workloadentries" ]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["authentication.k8s.io"]
resources: ["tokenreviews"]
verbs: ["create"]
- apiGroups: ["authorization.k8s.io"]
resources: ["subjectaccessreviews"]
verbs: ["create"]
- apiGroups: ["multicluster.x-k8s.io"]
resources: ["serviceexports"]
verbs: ["get", "watch", "list"]
- apiGroups: ["multicluster.x-k8s.io"]
resources: ["serviceimports"]
verbs: ["get", "watch", "list"]
{{- if or .Values.global.externalIstiod }}
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["create", "get", "list", "watch", "update"]
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["validatingwebhookconfigurations"]
verbs: ["get", "list", "watch", "update"]
{{- end}}
---

View File

@@ -0,0 +1,37 @@
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# DO NOT EDIT!
# THIS IS A LEGACY CHART HERE FOR BACKCOMPAT
# UPDATED CHART AT manifests/charts/istio-control/istio-discovery
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: istio-reader-{{ .Values.global.istioNamespace }}
labels:
app: istio-reader
release: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: istio-reader-{{ .Values.global.istioNamespace }}
subjects:
- kind: ServiceAccount
name: istio-reader-service-account
namespace: {{ .Values.global.istioNamespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: istiod-{{ .Values.global.istioNamespace }}
labels:
app: istiod
release: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: istiod-{{ .Values.global.istioNamespace }}
subjects:
- kind: ServiceAccount
name: istiod-service-account
namespace: {{ .Values.global.istioNamespace }}
---

View File

@@ -0,0 +1,4 @@
{{- if .Values.base.enableCRDTemplates }}
{{ .Files.Get "crds/crd-all.gen.yaml" }}
{{ .Files.Get "crds/crd-operator.yaml" }}
{{- end }}

View File

@@ -0,0 +1,48 @@
{{- if not (eq .Values.defaultRevision "") }}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: istiod-default-validator
labels:
app: istiod
release: {{ .Release.Name }}
istio: istiod
istio.io/rev: {{ .Values.defaultRevision }}
webhooks:
- name: validation.istio.io
clientConfig:
{{- if .Values.base.validationURL }}
url: {{ .Values.base.validationURL }}
{{- else }}
service:
{{- if (eq .Values.defaultRevision "default") }}
name: istiod
{{- else }}
name: istiod-{{ .Values.defaultRevision }}
{{- end }}
namespace: {{ .Values.global.istioNamespace }}
path: "/validate"
{{- end }}
rules:
- operations:
- CREATE
- UPDATE
apiGroups:
- security.istio.io
- networking.istio.io
- telemetry.istio.io
- extensions.istio.io
{{- if .Values.base.validateGateway }}
- gateway.networking.k8s.io
{{- end }}
apiVersions:
- "*"
resources:
- "*"
# Fail open until the validation webhook is ready. The webhook controller
# will update this to `Fail` and patch in the `caBundle` when the webhook
# endpoint is ready.
failurePolicy: Ignore
sideEffects: None
admissionReviewVersions: ["v1beta1", "v1"]
{{- end }}

View File

@@ -0,0 +1,23 @@
{{- if regexMatch "^([0-9]*\\.){3}[0-9]*$" .Values.global.remotePilotAddress }}
# if the remotePilotAddress is an IP addr
apiVersion: v1
kind: Endpoints
metadata:
{{- if .Values.pilot.enabled }}
name: istiod-remote
{{- else }}
name: istiod
{{- end }}
namespace: {{ .Release.Namespace }}
subsets:
- addresses:
- ip: {{ .Values.global.remotePilotAddress }}
ports:
- port: 15012
name: tcp-istiod
protocol: TCP
- port: 15017
name: tcp-webhook
protocol: TCP
---
{{- end }}

View File

@@ -0,0 +1,16 @@
# This service account aggregates reader permissions for the revisions in a given cluster
# Should be used for remote secret creation.
apiVersion: v1
kind: ServiceAccount
{{- if .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.global.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
metadata:
name: istio-reader-service-account
namespace: {{ .Values.global.istioNamespace }}
labels:
app: istio-reader
release: {{ .Release.Name }}

View File

@@ -0,0 +1,25 @@
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# DO NOT EDIT!
# THIS IS A LEGACY CHART HERE FOR BACKCOMPAT
# UPDATED CHART AT manifests/charts/istio-control/istio-discovery
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: istiod-{{ .Values.global.istioNamespace }}
namespace: {{ .Values.global.istioNamespace }}
labels:
app: istiod
release: {{ .Release.Name }}
rules:
# permissions to verify the webhook is ready and rejecting
# invalid config. We use --server-dry-run so no config is persisted.
- apiGroups: ["networking.istio.io"]
verbs: ["create"]
resources: ["gateways"]
# For storing CA secret
- apiGroups: [""]
resources: ["secrets"]
# TODO lock this down to istio-ca-cert if not using the DNS cert mesh config
verbs: ["create", "get", "watch", "list", "update", "delete"]

View File

@@ -0,0 +1,21 @@
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# DO NOT EDIT!
# THIS IS A LEGACY CHART HERE FOR BACKCOMPAT
# UPDATED CHART AT manifests/charts/istio-control/istio-discovery
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: istiod-{{ .Values.global.istioNamespace }}
namespace: {{ .Values.global.istioNamespace }}
labels:
app: istiod
release: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: istiod-{{ .Values.global.istioNamespace }}
subjects:
- kind: ServiceAccount
name: istiod-service-account
namespace: {{ .Values.global.istioNamespace }}

View File

@@ -0,0 +1,19 @@
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# DO NOT EDIT!
# THIS IS A LEGACY CHART HERE FOR BACKCOMPAT
# UPDATED CHART AT manifests/charts/istio-control/istio-discovery
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
apiVersion: v1
kind: ServiceAccount
{{- if .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.global.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
metadata:
name: istiod-service-account
namespace: {{ .Values.global.istioNamespace }}
labels:
app: istiod
release: {{ .Release.Name }}

View File

@@ -0,0 +1,28 @@
{{- if .Values.global.remotePilotAddress }}
apiVersion: v1
kind: Service
metadata:
{{- if .Values.pilot.enabled }}
# when local istiod is enabled, we can't use istiod service name to reach the remote control plane
name: istiod-remote
{{- else }}
# when local istiod isn't enabled, we can use istiod service name to reach the remote control plane
name: istiod
{{- end }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- port: 15012
name: tcp-istiod
protocol: TCP
- port: 443
targetPort: 15017
name: tcp-webhook
protocol: TCP
{{- if not (regexMatch "^([0-9]*\\.){3}[0-9]*$" .Values.global.remotePilotAddress) }}
# if the remotePilotAddress is not an IP addr, we use ExternalName
type: ExternalName
externalName: {{ .Values.global.remotePilotAddress }}
{{- end }}
---
{{- end }}

View File

@@ -0,0 +1,29 @@
global:
# ImagePullSecrets for control plane ServiceAccount, list of secrets in the same namespace
# to use for pulling any images in pods that reference this ServiceAccount.
# Must be set for any cluster configured with private docker registry.
imagePullSecrets: []
# Used to locate istiod.
istioNamespace: istio-system
istiod:
enableAnalysis: false
configValidation: true
externalIstiod: false
remotePilotAddress: ""
base:
# Used for helm2 to add the CRDs to templates.
enableCRDTemplates: false
# Validation webhook configuration url
# For example: https://$remotePilotAddress:15017/validate
validationURL: ""
# For istioctl usage to disable istio config crds in base
enableIstioConfigCRDs: true
defaultRevision: "default"

View File

@@ -23,6 +23,8 @@ import (
// FS embeds the manifests // FS embeds the manifests
// //
//go:embed profiles/* //go:embed profiles/*
//go:embed gatewayapi/*
//go:embed istiobase/*
var FS embed.FS var FS embed.FS
// BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in // BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in

View File

@@ -1,38 +1,16 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd. profile: all
#
# 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.
profile: kind
global: global:
install: local # install mode k8s/local/docker install: k8s # install mode k8s/local-k8s/local-docker/local
ingressClass: higress ingressClass: higress
watchNamespace:
disableAlpnH2: true
enableStatus: true
enableIstioAPI: true enableIstioAPI: true
enableGatewayAPI: false
namespace: higress-system namespace: higress-system
istioNamespace: istio-system
console: console:
port: 8080 port: 8080
replicas: 1 replicas: 1
serviceType: ClusterIP
domain: console.higress.io
tlsSecretName:
webLoginPrompt:
adminPasswordValue: admin adminPasswordValue: admin
adminPasswordLength: 8
o11yEnabled: false o11yEnabled: false
pvcRwxSupported: true
gateway: gateway:
replicas: 1 replicas: 1
@@ -44,7 +22,7 @@ controller:
replicas: 1 replicas: 1
storage: storage:
url: nacos://192.168.0.1:8848 # file://opt/higress/conf, buildin://127.0.0.1:8848 url: nacos://127.0.0.1:8848 # file://opt/higress/conf
ns: higress-system ns: higress-system
username: username:
password: password:
@@ -59,7 +37,7 @@ charts:
url: https://higress.io/helm-charts url: https://higress.io/helm-charts
name: higress name: higress
version: latest version: latest
istio: standalone:
url: https://istio-release.storage.googleapis.com/charts url: https://higress.io/standalone/get-higress.sh
name: base name: standalone
version: 1.18.2 version: latest

View File

@@ -1,37 +1,15 @@
# 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.
profile: k8s profile: k8s
global: global:
install: k8s # install mode k8s/local-k8s/local-docker/local install: k8s # install mode k8s/local-k8s/local-docker/local
ingressClass: higress ingressClass: higress
watchNamespace:
disableAlpnH2: true
enableStatus: true
enableIstioAPI: false enableIstioAPI: false
enableGatewayAPI: false
namespace: higress-system namespace: higress-system
istioNamespace: istio-system
console: console:
replicas: 1 replicas: 1
serviceType: ClusterIP
domain: console.higress.io
tlsSecretName:
webLoginPrompt:
adminPasswordValue: admin adminPasswordValue: admin
adminPasswordLength: 8
o11yEnabled: false o11yEnabled: false
pvcRwxSupported: true
gateway: gateway:
replicas: 2 replicas: 2
@@ -47,7 +25,7 @@ charts:
url: https://higress.io/helm-charts url: https://higress.io/helm-charts
name: higress name: higress
version: latest version: latest
istio: standalone:
url: https://istio-release.storage.googleapis.com/charts url: https://higress.io/standalone/get-higress.sh
name: base name: standalone
version: 1.18.2 version: latest

View File

@@ -0,0 +1,32 @@
profile: local-docker
global:
install: local-docker
console:
port: 8080
adminPasswordValue: admin
gateway:
httpPort: 80
httpsPort: 443
metricsPort: 15020
controller:
storage:
url: file://${INSTALLPACKAGEPATH}/conf
ns: higress-system
username:
password:
dataEncKey:
charts:
higress:
url: https://higress.io/helm-charts
name: higress
version: latest
standalone:
url: https://higress.io/standalone/get-higress.sh
name: standalone
version: latest

View File

@@ -1,37 +1,15 @@
# 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.
profile: local-k8s profile: local-k8s
global: global:
install: local-k8s # install mode k8s/local-k8s/local-docker/local install: local-k8s # install mode k8s/local-k8s/local-docker/local
ingressClass: higress ingressClass: higress
watchNamespace:
disableAlpnH2: true
enableStatus: true
enableIstioAPI: true enableIstioAPI: true
enableGatewayAPI: true
namespace: higress-system namespace: higress-system
istioNamespace: istio-system
console: console:
replicas: 1 replicas: 1
serviceType: ClusterIP
domain: console.higress.io
tlsSecretName:
webLoginPrompt:
adminPasswordValue: admin adminPasswordValue: admin
adminPasswordLength: 8
o11yEnabled: true o11yEnabled: true
pvcRwxSupported: true
gateway: gateway:
replicas: 1 replicas: 1
@@ -47,7 +25,7 @@ charts:
url: https://higress.io/helm-charts url: https://higress.io/helm-charts
name: higress name: higress
version: latest version: latest
istio: standalone:
url: https://istio-release.storage.googleapis.com/charts url: https://higress.io/standalone/get-higress.sh
name: base name: standalone
version: 1.18.2 version: latest

View File

@@ -21,7 +21,6 @@ import (
"github.com/alibaba/higress/pkg/cmd/hgctl/helm" "github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer" "github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options" "github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -29,18 +28,10 @@ import (
type uninstallArgs struct { type uninstallArgs struct {
// purgeIstioCRD delete all of Istio resources. // purgeIstioCRD delete all of Istio resources.
purgeIstioCRD bool purgeIstioCRD bool
// istioNamespace is the target namespace of istio control plane.
istioNamespace string
// namespace is the namespace of higress installed .
namespace string
} }
func addUninstallFlags(cmd *cobra.Command, args *uninstallArgs) { func addUninstallFlags(cmd *cobra.Command, args *uninstallArgs) {
cmd.PersistentFlags().StringVar(&args.istioNamespace, "istio-namespace", "istio-system", cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "", false,
"The namespace of Istio Control Plane.")
cmd.PersistentFlags().StringVarP(&args.namespace, "namespace", "n", "higress-system",
"The namespace of higress")
cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "p", false,
"Delete all of Istio resources") "Delete all of Istio resources")
} }
@@ -50,15 +41,13 @@ func newUninstallCmd() *cobra.Command {
uninstallCmd := &cobra.Command{ uninstallCmd := &cobra.Command{
Use: "uninstall", Use: "uninstall",
Short: "Uninstall higress from a cluster", Short: "Uninstall higress from a cluster",
Long: "The uninstall command uninstalls higress from a cluster", Long: "The uninstall command uninstalls higress from a cluster or local environment",
Example: ` # Uninstall higress Example: ` # Uninstall higress
hgctl uninstall hgctl uninstal
# Uninstall higress by special namespace # Uninstall higress and istio CRD from a cluster
hgctl uninstall --namespace=higress-system hgctl uninstall --purge-istio-crd
`,
# Uninstall higress and istio CRD
hgctl uninstall --purge-istio-crd --istio-namespace=istio-system`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return uninstall(cmd.OutOrStdout(), uiArgs) return uninstall(cmd.OutOrStdout(), uiArgs)
}, },
@@ -71,24 +60,36 @@ func newUninstallCmd() *cobra.Command {
// uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests. // uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests.
func uninstall(writer io.Writer, uiArgs *uninstallArgs) error { func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
profileName, ok := installer.GetInstalledYamlPath()
if !ok {
fmt.Fprintf(writer, "\nHigress hasn't been installed yet!\n")
return nil
}
setFlags := make([]string, 0) setFlags := make([]string, 0)
profileName := helm.GetUninstallProfileName()
_, profile, err := helm.GenProfile(profileName, "", setFlags) _, profile, err := helm.GenProfile(profileName, "", setFlags)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
err = profile.Validate()
if err != nil {
return err
}
if !promptUninstall(writer) { if !promptUninstall(writer) {
return nil return nil
} }
if profile.Global.Install == helm.InstallK8s || profile.Global.Install == helm.InstallLocalK8s {
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
profile.Global.Namespace = uiArgs.namespace }
profile.Global.IstioNamespace = uiArgs.istioNamespace
err = UnInstallManifests(profile, writer) err = uninstallManifests(profile, writer, uiArgs)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
@@ -108,29 +109,16 @@ func promptUninstall(writer io.Writer) bool {
} }
} }
func UnInstallManifests(profile *helm.Profile, writer io.Writer) error { func uninstallManifests(profile *helm.Profile, writer io.Writer, uiArgs *uninstallArgs) error {
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader()) installer, err := installer.NewInstaller(profile, writer, false)
if err != nil {
return fmt.Errorf("failed to build kubernetes client: %w", err)
}
op, err := installer.NewInstaller(profile, cliClient, writer, false)
if err != nil {
return err
}
if err := op.Run(); err != nil {
return err
}
manifestMap, err := op.RenderManifests()
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(writer, "\n⌛ Processing uninstallation... \n\n") err = installer.UnInstall()
if err := op.DeleteManifests(manifestMap); err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(writer, "\n🎊 Uninstall All Resources Complete!\n")
return nil return nil
} }

View File

@@ -15,6 +15,12 @@
package hgctl package hgctl
import ( import (
"fmt"
"io"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/options" "github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -24,6 +30,7 @@ type upgradeArgs struct {
} }
func addUpgradeFlags(cmd *cobra.Command, args *upgradeArgs) { func addUpgradeFlags(cmd *cobra.Command, args *upgradeArgs) {
cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr) cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr) cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
} }
@@ -39,7 +46,7 @@ func newUpgradeCmd() *cobra.Command {
Long: "The upgrade command is an alias for the install command" + Long: "The upgrade command is an alias for the install command" +
" that performs additional upgrade-related checks.", " that performs additional upgrade-related checks.",
RunE: func(cmd *cobra.Command, args []string) (e error) { RunE: func(cmd *cobra.Command, args []string) (e error) {
return Install(cmd.OutOrStdout(), upgradeArgs.InstallArgs) return upgrade(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
}, },
} }
addUpgradeFlags(upgradeCmd, upgradeArgs) addUpgradeFlags(upgradeCmd, upgradeArgs)
@@ -47,3 +54,70 @@ func newUpgradeCmd() *cobra.Command {
options.AddKubeConfigFlags(flags) options.AddKubeConfigFlags(flags)
return upgradeCmd return upgradeCmd
} }
// upgrade upgrade higress resources from the cluster.
func upgrade(writer io.Writer, iArgs *InstallArgs) error {
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
profileName, ok := installer.GetInstalledYamlPath()
if !ok {
fmt.Fprintf(writer, "\nHigress hasn't been installed yet!\n")
return nil
}
valuesOverlay, err := helm.GetValuesOverylayFromFiles(iArgs.InFilenames)
if err != nil {
return err
}
_, profile, err := helm.GenProfile(profileName, valuesOverlay, setFlags)
if err != nil {
return err
}
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
err = profile.Validate()
if err != nil {
return err
}
if !promptUpgrade(writer) {
return nil
}
err = upgradeManifests(profile, writer)
if err != nil {
return err
}
return nil
}
func promptUpgrade(writer io.Writer) bool {
answer := ""
for {
fmt.Fprintf(writer, "All Higress resources will be upgraed from the cluster. \nProceed? (y/N)")
fmt.Scanln(&answer)
if strings.TrimSpace(answer) == "y" {
fmt.Fprintf(writer, "\n")
return true
}
if strings.TrimSpace(answer) == "N" {
fmt.Fprintf(writer, "Cancelled.\n")
return false
}
}
}
func upgradeManifests(profile *helm.Profile, writer io.Writer) error {
installer, err := installer.NewInstaller(profile, writer, false)
if err != nil {
return err
}
err = installer.Upgrade()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,123 @@
// 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 util
import (
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"time"
)
const (
defaultInitialInterval = 500 * time.Millisecond
defaultMaxInterval = 60 * time.Second
)
type HTTPFetcher struct {
client *http.Client
initialBackoff time.Duration
requestMaxRetry int
bufferSize int64
}
// NewHTTPFetcher create a new HTTP remote fetcher.
func NewHTTPFetcher(requestTimeout time.Duration, requestMaxRetry int, bufferSize int64) *HTTPFetcher {
if requestTimeout == 0 {
requestTimeout = 5 * time.Second
}
transport := http.DefaultTransport.(*http.Transport).Clone()
// nolint: gosec
// This is only when a user explicitly sets a flag to enable insecure mode
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
return &HTTPFetcher{
client: &http.Client{
Timeout: requestTimeout,
},
initialBackoff: defaultInitialInterval,
requestMaxRetry: requestMaxRetry,
bufferSize: bufferSize,
}
}
// Fetch downloads with HTTP get.
func (f *HTTPFetcher) Fetch(ctx context.Context, url string) ([]byte, error) {
c := f.client
delayInterval := f.initialBackoff
attempts := 0
var lastError error
for attempts < f.requestMaxRetry {
attempts++
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := c.Do(req)
if err != nil {
lastError = err
if ctx.Err() != nil {
// If there is context timeout, exit this loop.
return nil, fmt.Errorf("download failed after %v attempts, last error: %v", attempts, lastError)
}
delayInterval = delayInterval + f.initialBackoff
if delayInterval > defaultMaxInterval {
break
}
time.Sleep(delayInterval)
continue
}
if resp.StatusCode == http.StatusOK {
body, err := io.ReadAll(io.LimitReader(resp.Body, f.bufferSize))
if err != nil {
return nil, err
}
err = resp.Body.Close()
if err != nil {
return nil, err
}
return body, err
}
lastError = fmt.Errorf("download request failed: status code %v", resp.StatusCode)
if retryable(resp.StatusCode) {
_, err := io.ReadAll(io.LimitReader(resp.Body, f.bufferSize))
if err != nil {
return nil, err
}
err = resp.Body.Close()
delayInterval = delayInterval + f.initialBackoff
if delayInterval > defaultMaxInterval {
break
}
time.Sleep(delayInterval)
continue
}
err = resp.Body.Close()
break
}
return nil, fmt.Errorf("download failed after %v attempts, last error: %v", attempts, lastError)
}
func retryable(code int) bool {
return code >= 500 &&
!(code == http.StatusNotImplemented ||
code == http.StatusHTTPVersionNotSupported ||
code == http.StatusNetworkAuthenticationRequired)
}

View File

@@ -15,8 +15,10 @@
package util package util
import ( import (
"bufio"
"fmt" "fmt"
"net/url" "net/url"
"os"
"strconv" "strconv"
"strings" "strings"
) )
@@ -76,3 +78,18 @@ func ParseValue(valueStr string) any {
} }
return value return value
} }
// WriteFileString write string content to file
func WriteFileString(fileName string, content string, perm os.FileMode) error {
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
if _, err := writer.WriteString(content); err != nil {
return err
}
writer.Flush()
return nil
}