mirror of
https://github.com/alibaba/higress.git
synced 2026-02-28 06:30:49 +08:00
feat:add installation for higress standalone in local docker environment (#606)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
This commit is contained in:
@@ -27,6 +27,7 @@ header:
|
||||
- 'tools/'
|
||||
- 'test/README.md'
|
||||
- 'pkg/cmd/hgctl/testdata/config'
|
||||
- 'pkg/cmd/hgctl/manifests'
|
||||
|
||||
comment: on-failure
|
||||
dependency:
|
||||
|
||||
3
go.mod
3
go.mod
@@ -172,6 +172,8 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // 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/ps v0.0.0-20150810152359-62de8c46ede0 // 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/statsd_exporter v0.21.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/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect
|
||||
|
||||
@@ -104,7 +104,8 @@ func newDashboardCmd() *cobra.Command {
|
||||
controllerDebugCmd := controllerDebugCmd()
|
||||
controllerDebugCmd.PersistentFlags().IntVar(&controllerPort, "ui-port", defaultControllerPort, "The component dashboard UI port.")
|
||||
dashboardCmd.AddCommand(controllerDebugCmd)
|
||||
|
||||
flags := dashboardCmd.PersistentFlags()
|
||||
options.AddKubeConfigFlags(flags)
|
||||
return dashboardCmd
|
||||
}
|
||||
|
||||
|
||||
@@ -26,15 +26,40 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// ReadYamlProfile gets the overlay yaml file from list of files and return profile value from file overlay and set overlay.
|
||||
func ReadYamlProfile(inFilenames []string, setFlags []string) (string, string, error) {
|
||||
// GetProfileFromFlags get profile name from flags.
|
||||
func GetProfileFromFlags(setFlags []string) (string, error) {
|
||||
profileName := DefaultProfileName
|
||||
// The profile coming from --set flag has the highest precedence.
|
||||
psf := GetValueForSetFlag(setFlags, "profile")
|
||||
if 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 {
|
||||
@@ -101,12 +126,17 @@ func GenerateConfig(inFilenames []string, setFlags []string) (string, *Profile,
|
||||
return "", nil, "", err
|
||||
}
|
||||
|
||||
fy, profileName, err := ReadYamlProfile(inFilenames, setFlags)
|
||||
profileName, err := GetProfileFromFlags(setFlags)
|
||||
if err != nil {
|
||||
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 {
|
||||
return "", nil, "", err
|
||||
|
||||
@@ -17,7 +17,9 @@ package helm
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"istio.io/istio/operator/pkg/util"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -43,83 +45,72 @@ type Profile struct {
|
||||
}
|
||||
|
||||
type ProfileGlobal struct {
|
||||
Install InstallMode `json:"install,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"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
IstioNamespace string `json:"istioNamespace,omitempty"`
|
||||
Install InstallMode `json:"install,omitempty"`
|
||||
IngressClass string `json:"ingressClass,omitempty"`
|
||||
EnableIstioAPI bool `json:"enableIstioAPI,omitempty"`
|
||||
EnableGatewayAPI bool `json:"enableGatewayAPI,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) {
|
||||
sets := make([]string, 0)
|
||||
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.istioNamespace=%s", p.IstioNamespace))
|
||||
if install == InstallLocalK8s {
|
||||
sets = append(sets, fmt.Sprintf("global.local=%t", true))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass))
|
||||
sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI))
|
||||
sets = append(sets, fmt.Sprintf("global.enableGatewayAPI=%t", p.EnableGatewayAPI))
|
||||
if install == InstallLocalK8s {
|
||||
sets = append(sets, fmt.Sprintf("global.local=%t", true))
|
||||
}
|
||||
}
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
func (p ProfileGlobal) Validate(install InstallMode) []error {
|
||||
errs := make([]error, 0)
|
||||
// now only support k8s and local-k8s installation mode
|
||||
if p.Install != InstallK8s && p.Install != InstallLocalK8s {
|
||||
errs = append(errs, errors.New("global.install only can be set to k8s or local-k8s"))
|
||||
// now only support k8s, local-k8s, local-docker installation mode
|
||||
if install != InstallK8s && install != InstallLocalK8s && install != InstallLocalDocker {
|
||||
errs = append(errs, errors.New("global.install only can be set to k8s, local-k8s or local-docker"))
|
||||
}
|
||||
if len(p.IngressClass) == 0 {
|
||||
errs = append(errs, errors.New("global.ingressClass can't be empty"))
|
||||
}
|
||||
if len(p.Namespace) == 0 {
|
||||
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"))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
if len(p.IngressClass) == 0 {
|
||||
errs = append(errs, errors.New("global.ingressClass can't be empty"))
|
||||
}
|
||||
if len(p.Namespace) == 0 {
|
||||
errs = append(errs, errors.New("global.namespace can't be empty"))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
type ProfileConsole struct {
|
||||
Port uint32 `json:"port,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"`
|
||||
AdminPasswordLength uint32 `json:"adminPasswordLength,omitempty"`
|
||||
O11yEnabled bool `json:"o11YEnabled,omitempty"`
|
||||
PvcRwxSupported bool `json:"pvcRwxSupported,omitempty"`
|
||||
Port uint32 `json:"port,omitempty"`
|
||||
Replicas uint32 `json:"replicas,omitempty"`
|
||||
AdminPasswordValue string `json:"adminPasswordValue,omitempty"`
|
||||
O11yEnabled bool `json:"o11YEnabled,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) {
|
||||
sets := make([]string, 0)
|
||||
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.length=%d", p.AdminPasswordLength))
|
||||
sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled))
|
||||
sets = append(sets, fmt.Sprintf("higress-console.pvc.rwxSupported=%t", p.PvcRwxSupported))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas))
|
||||
sets = append(sets, fmt.Sprintf("higress-console.admin.password.value=%s", p.AdminPasswordValue))
|
||||
sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled))
|
||||
}
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
func (p ProfileConsole) Validate(install InstallMode) []error {
|
||||
errs := make([]error, 0)
|
||||
if p.Replicas <= 0 {
|
||||
errs = append(errs, errors.New("console.replica need be large than zero"))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
if p.Replicas <= 0 {
|
||||
errs = append(errs, errors.New("console.replica need be large than zero"))
|
||||
}
|
||||
}
|
||||
|
||||
if p.ServiceType != "ClusterIP" && p.ServiceType != "NodePort" && p.ServiceType != "LoadBalancer" {
|
||||
errs = append(errs, errors.New("console.serviceType can only be set to ClusterIP, NodePort or LoadBalancer"))
|
||||
if install == InstallLocalDocker {
|
||||
if p.Port <= 0 {
|
||||
errs = append(errs, errors.New("console.port need be large than zero"))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
@@ -134,16 +125,31 @@ type ProfileGateway struct {
|
||||
|
||||
func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) {
|
||||
sets := make([]string, 0)
|
||||
sets = append(sets, fmt.Sprintf("higress-core.gateway.replicas=%d", p.Replicas))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
sets = append(sets, fmt.Sprintf("higress-core.gateway.replicas=%d", p.Replicas))
|
||||
}
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
func (p ProfileGateway) Validate(install InstallMode) []error {
|
||||
errs := make([]error, 0)
|
||||
if p.Replicas <= 0 {
|
||||
errs = append(errs, errors.New("gateway.replica need be large than zero"))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
if p.Replicas <= 0 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -153,16 +159,19 @@ type ProfileController struct {
|
||||
|
||||
func (p ProfileController) SetFlags(install InstallMode) ([]string, error) {
|
||||
sets := make([]string, 0)
|
||||
sets = append(sets, fmt.Sprintf("higress-core.controller.replicas=%d", p.Replicas))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
sets = append(sets, fmt.Sprintf("higress-core.controller.replicas=%d", p.Replicas))
|
||||
}
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
func (p ProfileController) Validate(install InstallMode) []error {
|
||||
errs := make([]error, 0)
|
||||
if p.Replicas <= 0 {
|
||||
errs = append(errs, errors.New("controller.replica need be large than zero"))
|
||||
if install == InstallK8s || install == InstallLocalK8s {
|
||||
if p.Replicas <= 0 {
|
||||
errs = append(errs, errors.New("controller.replica need be large than zero"))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -176,6 +185,31 @@ type ProfileStorage struct {
|
||||
|
||||
func (p ProfileStorage) Validate(install InstallMode) []error {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -186,8 +220,8 @@ type Chart struct {
|
||||
}
|
||||
|
||||
type ProfileCharts struct {
|
||||
Higress Chart `json:"higress,omitempty"`
|
||||
Istio Chart `json:"istio,omitempty"`
|
||||
Higress Chart `json:"higress,omitempty"`
|
||||
Standalone Chart `json:"standalone,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProfileCharts) Validate(install InstallMode) []error {
|
||||
@@ -222,8 +256,13 @@ func (p *Profile) ValuesYaml() (string, error) {
|
||||
}
|
||||
valueOverlayYAML = string(out)
|
||||
}
|
||||
|
||||
flagsYAML, err := overlaySetFlagValues("", setFlags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// merge values and setFlags
|
||||
overlayYAML, err := overlaySetFlagValues(valueOverlayYAML, setFlags)
|
||||
overlayYAML, err := util.OverlayYAML(flagsYAML, valueOverlayYAML)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -237,6 +276,26 @@ func (p *Profile) IstioEnabled() bool {
|
||||
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 {
|
||||
errs := make([]error, 0)
|
||||
errsGlobal := p.Global.Validate(p.Global.Install)
|
||||
@@ -256,7 +315,7 @@ func (p *Profile) Validate() error {
|
||||
errs = append(errs, errsController...)
|
||||
}
|
||||
errsStorage := p.Storage.Validate(p.Global.Install)
|
||||
if len(errsController) > 0 {
|
||||
if len(errsStorage) > 0 {
|
||||
errs = append(errs, errsStorage...)
|
||||
}
|
||||
errsCharts := p.Charts.Validate(p.Global.Install)
|
||||
|
||||
@@ -127,7 +127,7 @@ type RendererOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
|
||||
// fields for LocalRenderer
|
||||
// fields for LocalChartRenderer and LocalFileRenderer
|
||||
FS fs.FS
|
||||
Dir string
|
||||
|
||||
@@ -174,14 +174,84 @@ func WithRepoURL(repo string) RendererOption {
|
||||
}
|
||||
}
|
||||
|
||||
// LocalRenderer load chart from local file system
|
||||
type LocalRenderer struct {
|
||||
// LocalFileRenderer load yaml files from local file system
|
||||
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
|
||||
Chart *chart.Chart
|
||||
Started bool
|
||||
}
|
||||
|
||||
func (lr *LocalRenderer) Init() error {
|
||||
func (lr *LocalChartRenderer) Init() error {
|
||||
fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -212,18 +282,18 @@ func (lr *LocalRenderer) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LocalRenderer) RenderManifest(valsYaml string) (string, error) {
|
||||
func (lr *LocalChartRenderer) RenderManifest(valsYaml string) (string, error) {
|
||||
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...)
|
||||
}
|
||||
|
||||
func (lr *LocalRenderer) SetVersion(version string) {
|
||||
func (lr *LocalChartRenderer) SetVersion(version string) {
|
||||
lr.Opts.Version = version
|
||||
}
|
||||
|
||||
func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
|
||||
func NewLocalChartRenderer(opts ...RendererOption) (Renderer, error) {
|
||||
newOpts := &RendererOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(newOpts)
|
||||
@@ -232,7 +302,7 @@ func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
|
||||
if err := verifyRendererOptions(newOpts); err != nil {
|
||||
return nil, fmt.Errorf("verify err: %s", err)
|
||||
}
|
||||
return &LocalRenderer{
|
||||
return &LocalChartRenderer{
|
||||
Opts: newOpts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
@@ -32,13 +31,16 @@ const (
|
||||
// manifestsFlagHelpStr is the command line description for --manifests
|
||||
manifestsFlagHelpStr = `Specify a path to a directory of profiles
|
||||
(e.g. ~/Downloads/higress/manifests).`
|
||||
outputHelpstr = "Specify a file to write profile yaml"
|
||||
filenameFlagHelpStr = "Path to file containing helm custom values"
|
||||
outputHelpstr = "Specify a file to write profile yaml"
|
||||
|
||||
profileNameK8s = "k8s"
|
||||
profileNameLocalK8s = "local-k8s"
|
||||
profileNameK8s = "k8s"
|
||||
profileNameLocalK8s = "local-k8s"
|
||||
profileNameLocalDocker = "local-docker"
|
||||
)
|
||||
|
||||
type InstallArgs struct {
|
||||
// InFilenames is a filename to helm custom values
|
||||
InFilenames []string
|
||||
// KubeConfigPath is the path to kube config file.
|
||||
KubeConfigPath string
|
||||
@@ -61,6 +63,7 @@ func (a *InstallArgs) String() string {
|
||||
}
|
||||
|
||||
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().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||
}
|
||||
@@ -87,19 +90,23 @@ func newInstallCmd() *cobra.Command {
|
||||
# Install higress on local kubernetes cluster
|
||||
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
|
||||
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
|
||||
|
||||
# To override helm setting
|
||||
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m"
|
||||
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(0),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return Install(cmd.OutOrStdout(), iArgs)
|
||||
return install(cmd.OutOrStdout(), iArgs)
|
||||
},
|
||||
}
|
||||
addInstallFlags(installCmd, iArgs)
|
||||
@@ -108,7 +115,7 @@ func newInstallCmd() *cobra.Command {
|
||||
return installCmd
|
||||
}
|
||||
|
||||
func Install(writer io.Writer, iArgs *InstallArgs) error {
|
||||
func install(writer io.Writer, iArgs *InstallArgs) error {
|
||||
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
|
||||
|
||||
// check profileName
|
||||
@@ -133,7 +140,7 @@ func Install(writer io.Writer, iArgs *InstallArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = InstallManifests(profile, writer)
|
||||
err = installManifests(profile, writer)
|
||||
if err != nil {
|
||||
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 {
|
||||
answer := ""
|
||||
fmt.Fprintf(writer, "Please select higress install configration profile:\n")
|
||||
fmt.Fprintf(writer, "1.Install higress to local kubernetes cluster like kind etc.\n")
|
||||
fmt.Fprintf(writer, "2.Install higress to kubernetes cluster\n")
|
||||
fmt.Fprintf(writer, "\nPlease select higress install configration profile:\n")
|
||||
fmt.Fprintf(writer, "\n1.Install higress to local kubernetes cluster like kind etc.\n")
|
||||
fmt.Fprintf(writer, "\n2.Install higress to kubernetes cluster\n")
|
||||
fmt.Fprintf(writer, "\n3.Install higress to local docker environment\n")
|
||||
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)
|
||||
if strings.TrimSpace(answer) == "1" {
|
||||
return profileNameLocalK8s
|
||||
@@ -170,33 +178,23 @@ func promptProfileName(writer io.Writer) string {
|
||||
if strings.TrimSpace(answer) == "2" {
|
||||
return profileNameK8s
|
||||
}
|
||||
if strings.TrimSpace(answer) == "3" {
|
||||
return profileNameLocalDocker
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func InstallManifests(profile *helm.Profile, writer io.Writer) error {
|
||||
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||
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()
|
||||
func installManifests(profile *helm.Profile, writer io.Writer) error {
|
||||
installer, err := installer.NewInstaller(profile, writer, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "\n⌛️ Processing installation... \n\n")
|
||||
if err := op.ApplyManifests(manifestMap); err != nil {
|
||||
err = installer.Install()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(writer, "\n🎊 Install All Resources Complete!\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
108
pkg/cmd/hgctl/installer/gateway_api.go
Normal file
108
pkg/cmd/hgctl/installer/gateway_api.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
|
||||
)
|
||||
|
||||
const (
|
||||
GatewayAPI ComponentName = "gatewayAPI"
|
||||
)
|
||||
|
||||
type GatewayAPIComponent struct {
|
||||
profile *helm.Profile
|
||||
started bool
|
||||
opts *ComponentOptions
|
||||
renderer helm.Renderer
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewGatewayAPIComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
|
||||
newOpts := &ComponentOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(newOpts)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(newOpts.RepoURL, "embed://") {
|
||||
return nil, errors.New("GatewayAPI Url need start with embed://")
|
||||
}
|
||||
|
||||
chartDir := strings.TrimPrefix(newOpts.RepoURL, "embed://")
|
||||
// GatewayAPI can only be installed by embed type
|
||||
renderer, err := helm.NewLocalFileRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(manifests.BuiltinOrDir("")),
|
||||
helm.WithDir(chartDir),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gatewayAPIComponent := &GatewayAPIComponent{
|
||||
profile: profile,
|
||||
renderer: renderer,
|
||||
opts: newOpts,
|
||||
writer: writer,
|
||||
}
|
||||
return gatewayAPIComponent, nil
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) ComponentName() ComponentName {
|
||||
return GatewayAPI
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) Namespace() string {
|
||||
return i.opts.Namespace
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) Run() error {
|
||||
if !i.opts.Quiet {
|
||||
fmt.Fprintf(i.writer, "🏄 Downloading GatewayAPI Yaml Files version: %s, url: %s\n", i.opts.Version, i.opts.RepoURL)
|
||||
}
|
||||
if err := i.renderer.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
i.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *GatewayAPIComponent) RenderManifest() (string, error) {
|
||||
if !i.started {
|
||||
return "", nil
|
||||
}
|
||||
if !i.opts.Quiet {
|
||||
fmt.Fprintf(i.writer, "📦 Rendering GatewayAPI Yaml Files\n")
|
||||
}
|
||||
values := make(map[string]any)
|
||||
manifest, err := renderComponentManifest(values, i.renderer, false, i.ComponentName(), i.opts.Namespace)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
@@ -15,11 +15,10 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -96,29 +95,19 @@ func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...Compon
|
||||
opt(newOpts)
|
||||
}
|
||||
|
||||
var renderer helm.Renderer
|
||||
var err error
|
||||
if newOpts.RepoURL != "" {
|
||||
renderer, err = helm.NewRemoteRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
renderer, err = helm.NewLocalRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
||||
helm.WithDir(string(Higress)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(newOpts.RepoURL) == 0 {
|
||||
return nil, errors.New("Higress helm chart url can't be empty")
|
||||
}
|
||||
|
||||
// Higress can only be installed by remote type
|
||||
renderer, err := helm.NewRemoteRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
higressComponent := &HigressComponent{
|
||||
|
||||
@@ -18,196 +18,113 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||
"github.com/alibaba/higress/pkg/cmd/options"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
type Installer struct {
|
||||
started bool
|
||||
components map[ComponentName]Component
|
||||
kubeCli kubernetes.CLIClient
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
const (
|
||||
HgctlHomeDirPath = ".hgctl"
|
||||
StandaloneInstalledPath = "higress-standalone"
|
||||
ProfileInstalledPath = "profiles"
|
||||
InstalledYamlFileName = "install.yaml"
|
||||
DefaultGatewayAPINamespace = "gateway-system"
|
||||
DefaultIstioNamespace = "istio-system"
|
||||
)
|
||||
|
||||
type Installer interface {
|
||||
Install() error
|
||||
UnInstall() error
|
||||
Upgrade() error
|
||||
}
|
||||
|
||||
// Run must be invoked before invoking other functions.
|
||||
func (o *Installer) Run() error {
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
if err := component.Run(); err != nil {
|
||||
return fmt.Errorf("component %s run failed, err: %s", name, err)
|
||||
}
|
||||
}
|
||||
o.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderManifests renders component manifests specified by profile.
|
||||
func (o *Installer) RenderManifests() (map[ComponentName]string, error) {
|
||||
if !o.started {
|
||||
return nil, errors.New("HigressOperator is not running")
|
||||
}
|
||||
res := make(map[ComponentName]string)
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
manifest, err := component.RenderManifest()
|
||||
func NewInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (Installer, error) {
|
||||
switch profile.Global.Install {
|
||||
case helm.InstallK8s, helm.InstallLocalK8s:
|
||||
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
|
||||
return nil, fmt.Errorf("failed to build kubernetes client: %w", err)
|
||||
}
|
||||
res[name] = manifest
|
||||
installer, err := NewK8sInstaller(profile, cliClient, writer, quiet)
|
||||
return installer, err
|
||||
case helm.InstallLocalDocker:
|
||||
installer, err := NewDockerInstaller(profile, writer, quiet)
|
||||
return installer, err
|
||||
default:
|
||||
return nil, errors.New("install is not supported")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GenerateManifests generates component manifests to k8s cluster
|
||||
func (o *Installer) GenerateManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into Installer")
|
||||
func GetHomeDir() (string, error) {
|
||||
home := homedir.HomeDir()
|
||||
if home == "" {
|
||||
return "", fmt.Errorf("No user home environment variable found for OS %s", runtime.GOOS)
|
||||
}
|
||||
for _, manifest := range manifestMap {
|
||||
fmt.Fprint(o.writer, manifest)
|
||||
}
|
||||
return nil
|
||||
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// ApplyManifests apply component manifests to k8s cluster
|
||||
func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into Installer")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.applyManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Installer) applyManifest(manifest string, ns string) error {
|
||||
if err := o.kubeCli.CreateNamespace(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
func GetHgctlPath() (string, error) {
|
||||
home, err := GetHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
|
||||
hgctlPath := filepath.Join(home, HgctlHomeDirPath)
|
||||
if _, err := os.Stat(hgctlPath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(hgctlPath, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return hgctlPath, nil
|
||||
}
|
||||
|
||||
// DeleteManifests delete component manifests to k8s cluster
|
||||
func (o *Installer) DeleteManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into Installer")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.deleteManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteManifest delete manifest to certain namespace
|
||||
func (o *Installer) deleteManifest(manifest string, ns string) error {
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
func GetDefaultInstallPackagePath() (string, error) {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
|
||||
path := filepath.Join(dir, StandaloneInstalledPath)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return path, err
|
||||
}
|
||||
|
||||
func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool {
|
||||
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*Installer, error) {
|
||||
if profile == nil {
|
||||
return nil, errors.New("install profile is empty")
|
||||
}
|
||||
// initialize components
|
||||
components := make(map[ComponentName]Component)
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(profile.Global.Namespace),
|
||||
WithComponentChartPath(profile.InstallPackagePath),
|
||||
WithComponentVersion(profile.Charts.Higress.Version),
|
||||
WithComponentRepoURL(profile.Charts.Higress.Url),
|
||||
WithComponentChartName(profile.Charts.Higress.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
higressComponent, err := NewHigressComponent(profile, writer, opts...)
|
||||
func GetProfileInstalledPath() (string, error) {
|
||||
hgctlPath, err := GetHgctlPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
|
||||
return "", err
|
||||
}
|
||||
components[Higress] = higressComponent
|
||||
|
||||
if profile.IstioEnabled() {
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(profile.Global.IstioNamespace),
|
||||
WithComponentChartPath(profile.InstallPackagePath),
|
||||
WithComponentVersion(profile.Charts.Istio.Version),
|
||||
WithComponentRepoURL(profile.Charts.Istio.Url),
|
||||
WithComponentChartName(profile.Charts.Istio.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
profilesPath := filepath.Join(hgctlPath, ProfileInstalledPath)
|
||||
if _, err := os.Stat(profilesPath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(profilesPath, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
|
||||
}
|
||||
components[Istio] = istioCRDComponent
|
||||
}
|
||||
op := &Installer{
|
||||
profile: profile,
|
||||
components: components,
|
||||
kubeCli: cli,
|
||||
writer: writer,
|
||||
}
|
||||
return op, nil
|
||||
return profilesPath, nil
|
||||
}
|
||||
|
||||
func GetInstalledYamlPath() (string, bool) {
|
||||
profileInstalledPath, err := GetProfileInstalledPath()
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
installedYamlFile := filepath.Join(profileInstalledPath, InstalledYamlFileName)
|
||||
if _, err := os.Stat(installedYamlFile); os.IsNotExist(err) {
|
||||
return installedYamlFile, false
|
||||
}
|
||||
return installedYamlFile, true
|
||||
}
|
||||
|
||||
101
pkg/cmd/hgctl/installer/installer_docker.go
Normal file
101
pkg/cmd/hgctl/installer/installer_docker.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type DockerInstaller struct {
|
||||
started bool
|
||||
standalone *StandaloneComponent
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (d *DockerInstaller) Install() error {
|
||||
fmt.Fprintf(d.writer, "\n⌛️ Processing installation... \n\n")
|
||||
|
||||
if err := d.standalone.Install(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(d.writer, "\n✔️ Wrote Profile: \"%s\" \n", profileName)
|
||||
if err := util.WriteFileString(profileName, util.ToYAML(d.profile), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(d.writer, "\n🎊 Install All Resources Complete!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerInstaller) UnInstall() error {
|
||||
|
||||
fmt.Fprintf(d.writer, "\n⌛️ Processing uninstallation... \n\n")
|
||||
|
||||
if err := d.standalone.UnInstall(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(d.writer, "\n✔️ Removed Profile: \"%s\" \n", profileName)
|
||||
os.Remove(profileName)
|
||||
|
||||
fmt.Fprintf(d.writer, "\n🎊 Uninstall All Resources Complete!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerInstaller) Upgrade() error {
|
||||
fmt.Fprintf(d.writer, "\n⌛️ Processing upgrade... \n\n")
|
||||
|
||||
if err := d.standalone.Upgrade(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(d.writer, "\n🎊 Install All Resources Complete!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDockerInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (*DockerInstaller, error) {
|
||||
if profile == nil {
|
||||
return nil, errors.New("install profile is empty")
|
||||
}
|
||||
// initialize components
|
||||
opts := []ComponentOption{
|
||||
WithComponentVersion(profile.Charts.Standalone.Version),
|
||||
WithComponentRepoURL(profile.Charts.Standalone.Url),
|
||||
WithComponentChartName(profile.Charts.Standalone.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
standaloneComponent, err := NewStandaloneComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewStandaloneComponent failed, err: %s", err)
|
||||
}
|
||||
|
||||
op := &DockerInstaller{
|
||||
profile: profile,
|
||||
standalone: standaloneComponent,
|
||||
writer: writer,
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
299
pkg/cmd/hgctl/installer/installer_k8s.go
Normal file
299
pkg/cmd/hgctl/installer/installer_k8s.go
Normal file
@@ -0,0 +1,299 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||
)
|
||||
|
||||
type K8sInstaller struct {
|
||||
started bool
|
||||
components map[ComponentName]Component
|
||||
kubeCli kubernetes.CLIClient
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) Install() error {
|
||||
if _, err := GetProfileInstalledPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := o.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestMap, err := o.RenderManifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.writer, "\n⌛️ Processing installation... \n\n")
|
||||
if err := o.ApplyManifests(manifestMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(o.writer, "\n✔️ Wrote Profile: \"%s\" \n", profileName)
|
||||
if err := util.WriteFileString(profileName, util.ToYAML(o.profile), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.writer, "\n🎊 Install All Resources Complete!\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) UnInstall() error {
|
||||
if _, err := GetProfileInstalledPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := o.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestMap, err := o.RenderManifests()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.writer, "\n⌛️ Processing uninstallation... \n\n")
|
||||
if err := o.DeleteManifests(manifestMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profileName, _ := GetInstalledYamlPath()
|
||||
fmt.Fprintf(o.writer, "\n✔️ Removed Profile: \"%s\" \n", profileName)
|
||||
os.Remove(profileName)
|
||||
|
||||
fmt.Fprintf(o.writer, "\n🎊 Uninstall All Resources Complete!\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) Upgrade() error {
|
||||
return o.Install()
|
||||
}
|
||||
|
||||
// Run must be invoked before invoking other functions.
|
||||
func (o *K8sInstaller) Run() error {
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
if err := component.Run(); err != nil {
|
||||
return fmt.Errorf("component %s run failed, err: %s", name, err)
|
||||
}
|
||||
}
|
||||
o.started = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderManifests renders component manifests specified by profile.
|
||||
func (o *K8sInstaller) RenderManifests() (map[ComponentName]string, error) {
|
||||
if !o.started {
|
||||
return nil, errors.New("higress installer is not running")
|
||||
}
|
||||
res := make(map[ComponentName]string)
|
||||
for name, component := range o.components {
|
||||
if !component.Enabled() {
|
||||
continue
|
||||
}
|
||||
manifest, err := component.RenderManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
|
||||
}
|
||||
res[name] = manifest
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GenerateManifests generates component manifests to k8s cluster
|
||||
func (o *K8sInstaller) GenerateManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into K8sInstaller")
|
||||
}
|
||||
for _, manifest := range manifestMap {
|
||||
fmt.Fprint(o.writer, manifest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyManifests apply component manifests to k8s cluster
|
||||
func (o *K8sInstaller) ApplyManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into K8sInstaller")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.applyManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) applyManifest(manifest string, ns string) error {
|
||||
if err := o.kubeCli.CreateNamespace(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteManifests delete component manifests to k8s cluster
|
||||
func (o *K8sInstaller) DeleteManifests(manifestMap map[ComponentName]string) error {
|
||||
if o.kubeCli == nil {
|
||||
return errors.New("no injected k8s cli into K8sInstaller")
|
||||
}
|
||||
for name, manifest := range manifestMap {
|
||||
namespace := o.components[name].Namespace()
|
||||
if err := o.deleteManifest(manifest, namespace); err != nil {
|
||||
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteManifest delete manifest to certain namespace
|
||||
func (o *K8sInstaller) deleteManifest(manifest string, ns string) error {
|
||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// check namespaced object if namespace property has been existed
|
||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
||||
obj.Namespace = ns
|
||||
obj.UnstructuredObject().SetNamespace(ns)
|
||||
}
|
||||
if o.isNamespacedObject(obj) {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
||||
} else {
|
||||
fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name)
|
||||
}
|
||||
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *K8sInstaller) isNamespacedObject(obj *object.K8sObject) bool {
|
||||
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewK8sInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*K8sInstaller, error) {
|
||||
if profile == nil {
|
||||
return nil, errors.New("install profile is empty")
|
||||
}
|
||||
// initialize components
|
||||
components := make(map[ComponentName]Component)
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(profile.Global.Namespace),
|
||||
WithComponentChartPath(profile.InstallPackagePath),
|
||||
WithComponentVersion(profile.Charts.Higress.Version),
|
||||
WithComponentRepoURL(profile.Charts.Higress.Url),
|
||||
WithComponentChartName(profile.Charts.Higress.Name),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
higressComponent, err := NewHigressComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
|
||||
}
|
||||
components[Higress] = higressComponent
|
||||
|
||||
if profile.IstioEnabled() {
|
||||
istioNamespace := profile.GetIstioNamespace()
|
||||
if len(istioNamespace) == 0 {
|
||||
istioNamespace = DefaultIstioNamespace
|
||||
}
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(istioNamespace),
|
||||
WithComponentVersion("1.18.2"),
|
||||
WithComponentRepoURL("embed://istiobase"),
|
||||
WithComponentChartName("istio"),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
|
||||
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
|
||||
}
|
||||
components[Istio] = istioCRDComponent
|
||||
}
|
||||
|
||||
if profile.GatewayAPIEnabled() {
|
||||
opts := []ComponentOption{
|
||||
WithComponentNamespace(DefaultGatewayAPINamespace),
|
||||
WithComponentVersion("1.0.0"),
|
||||
WithComponentRepoURL("embed://gatewayapi"),
|
||||
WithComponentChartName("gatewayAPI"),
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, WithQuiet())
|
||||
}
|
||||
|
||||
gatewayAPIComponent, err := NewGatewayAPIComponent(profile, writer, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewGatewayAPIComponent failed, err: %s", err)
|
||||
}
|
||||
components[GatewayAPI] = gatewayAPIComponent
|
||||
}
|
||||
|
||||
op := &K8sInstaller{
|
||||
profile: profile,
|
||||
components: components,
|
||||
kubeCli: cli,
|
||||
writer: writer,
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
@@ -17,9 +17,10 @@ package installer
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,23 +43,27 @@ func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...Compo
|
||||
|
||||
var renderer helm.Renderer
|
||||
var err error
|
||||
if newOpts.RepoURL != "" {
|
||||
renderer, err = helm.NewRemoteRenderer(
|
||||
|
||||
// Istio can be installed by embed type or remote type
|
||||
if strings.HasPrefix(newOpts.RepoURL, "embed://") {
|
||||
chartDir := strings.TrimPrefix(newOpts.RepoURL, "embed://")
|
||||
renderer, err = helm.NewLocalChartRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(manifests.BuiltinOrDir("")),
|
||||
helm.WithDir(chartDir),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
renderer, err = helm.NewLocalRenderer(
|
||||
renderer, err = helm.NewRemoteRenderer(
|
||||
helm.WithName(newOpts.ChartName),
|
||||
helm.WithNamespace(newOpts.Namespace),
|
||||
helm.WithRepoURL(newOpts.RepoURL),
|
||||
helm.WithVersion(newOpts.Version),
|
||||
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
||||
helm.WithDir(string(Istio)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
139
pkg/cmd/hgctl/installer/standalone.go
Normal file
139
pkg/cmd/hgctl/installer/standalone.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package installer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHttpRequestTimeout = 15 * time.Second
|
||||
defaultHttpMaxTry = 3
|
||||
defaultHttpBufferSize = 1024 * 1024 * 2
|
||||
)
|
||||
|
||||
type StandaloneComponent struct {
|
||||
profile *helm.Profile
|
||||
started bool
|
||||
opts *ComponentOptions
|
||||
writer io.Writer
|
||||
httpFetcher *util.HTTPFetcher
|
||||
agent *Agent
|
||||
}
|
||||
|
||||
func (s *StandaloneComponent) Install() error {
|
||||
if !s.opts.Quiet {
|
||||
fmt.Fprintf(s.writer, "\n🏄 Downloading installer from %s\n", s.opts.RepoURL)
|
||||
}
|
||||
// download get-higress.sh
|
||||
data, err := s.httpFetcher.Fetch(context.Background(), s.opts.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// write installer binary shell
|
||||
if err := util.WriteFileString(s.agent.installBinaryName, string(data), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
// start to install higress
|
||||
if err := s.agent.Install(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StandaloneComponent) UnInstall() error {
|
||||
if err := s.agent.Uninstall(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StandaloneComponent) Upgrade() error {
|
||||
if !s.opts.Quiet {
|
||||
fmt.Fprintf(s.writer, "\n🏄 Downloading installer from %s\n", s.opts.RepoURL)
|
||||
}
|
||||
// download get-higress.sh
|
||||
data, err := s.httpFetcher.Fetch(context.Background(), s.opts.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// write installer binary shell
|
||||
if err := util.WriteFileString(s.agent.installBinaryName, string(data), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
// start to upgrade higress
|
||||
if err := s.agent.Upgrade(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewStandaloneComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (*StandaloneComponent, error) {
|
||||
newOpts := &ComponentOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(newOpts)
|
||||
}
|
||||
|
||||
httpFetcher := util.NewHTTPFetcher(defaultHttpRequestTimeout, defaultHttpMaxTry, defaultHttpBufferSize)
|
||||
if err := prepareProfile(profile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agent := NewAgent(profile, writer, newOpts.Quiet)
|
||||
standaloneComponent := &StandaloneComponent{
|
||||
profile: profile,
|
||||
opts: newOpts,
|
||||
writer: writer,
|
||||
httpFetcher: httpFetcher,
|
||||
agent: agent,
|
||||
}
|
||||
return standaloneComponent, nil
|
||||
}
|
||||
|
||||
func prepareProfile(profile *helm.Profile) error {
|
||||
if _, err := GetProfileInstalledPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(profile.InstallPackagePath) == 0 {
|
||||
dir, err := GetDefaultInstallPackagePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
profile.InstallPackagePath = dir
|
||||
}
|
||||
|
||||
if _, err := os.Stat(profile.InstallPackagePath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(profile.InstallPackagePath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// parse INSTALLPACKAGEPATH in storage.url
|
||||
if strings.HasPrefix(profile.Storage.Url, "file://") {
|
||||
profile.Storage.Url = strings.ReplaceAll(profile.Storage.Url, "${INSTALLPACKAGEPATH}", profile.InstallPackagePath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
357
pkg/cmd/hgctl/installer/standalone_agent.go
Normal file
357
pkg/cmd/hgctl/installer/standalone_agent.go
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package installer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
)
|
||||
|
||||
type RunSudoState string
|
||||
|
||||
const (
|
||||
NoSudo RunSudoState = "NoSudo"
|
||||
SudoWithoutPassword RunSudoState = "SudoWithoutPassword"
|
||||
SudoWithPassword RunSudoState = "SudoWithPassword"
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
profile *helm.Profile
|
||||
writer io.Writer
|
||||
shutdownBinaryName string
|
||||
resetBinaryName string
|
||||
startupBinaryName string
|
||||
installBinaryName string
|
||||
installPath string
|
||||
configuredPath string
|
||||
higressPath string
|
||||
versionPath string
|
||||
quiet bool
|
||||
runSudoState RunSudoState
|
||||
}
|
||||
|
||||
func NewAgent(profile *helm.Profile, writer io.Writer, quiet bool) *Agent {
|
||||
installPath := profile.InstallPackagePath
|
||||
return &Agent{
|
||||
profile: profile,
|
||||
writer: writer,
|
||||
installPath: installPath,
|
||||
higressPath: filepath.Join(installPath, "higress"),
|
||||
installBinaryName: filepath.Join(installPath, "get-higress.sh"),
|
||||
shutdownBinaryName: filepath.Join(installPath, "higress", "bin", "shutdown.sh"),
|
||||
resetBinaryName: filepath.Join(installPath, "higress", "bin", "reset.sh"),
|
||||
startupBinaryName: filepath.Join(installPath, "higress", "bin", "startup.sh"),
|
||||
configuredPath: filepath.Join(installPath, "higress", "compose", ".configured"),
|
||||
versionPath: filepath.Join(installPath, "higress", "VERSION"),
|
||||
quiet: quiet,
|
||||
runSudoState: NoSudo,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) profileArgs() []string {
|
||||
args := []string{
|
||||
fmt.Sprintf("--nacos-ns=%s", a.profile.Storage.Ns),
|
||||
fmt.Sprintf("--config-url=%s", a.profile.Storage.Url),
|
||||
fmt.Sprintf("--nacos-ns=%s", a.profile.Storage.Ns),
|
||||
fmt.Sprintf("--nacos-password=%s", a.profile.Storage.Password),
|
||||
fmt.Sprintf("--nacos-username=%s", a.profile.Storage.Username),
|
||||
fmt.Sprintf("--data-enc-key=%s", a.profile.Storage.DataEncKey),
|
||||
fmt.Sprintf("--console-password=%s", a.profile.Console.AdminPasswordValue),
|
||||
fmt.Sprintf("--console-port=%d", a.profile.Console.Port),
|
||||
fmt.Sprintf("--gateway-http-port=%d", a.profile.Gateway.HttpPort),
|
||||
fmt.Sprintf("--gateway-https-port=%d", a.profile.Gateway.HttpsPort),
|
||||
fmt.Sprintf("--gateway-metrics-port=%d", a.profile.Gateway.MetricsPort),
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (a *Agent) run(binaryName string, args []string, autoSudo bool) error {
|
||||
var cmd *exec.Cmd
|
||||
if !autoSudo || a.runSudoState == NoSudo {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n📦 Running command: %s %s\n\n", binaryName, strings.Join(args, " "))
|
||||
}
|
||||
cmd = exec.Command(binaryName, args...)
|
||||
} else {
|
||||
newArgs := make([]string, 0)
|
||||
newArgs = append(newArgs, binaryName)
|
||||
newArgs = append(newArgs, args...)
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n📦 Running command: %s %s\n\n", "sudo", strings.Join(newArgs, " "))
|
||||
}
|
||||
cmd = exec.Command("sudo", newArgs...)
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = a.installPath
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) checkSudoPermission() error {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Checking docker command sudo permission... ")
|
||||
}
|
||||
// check docker ps command
|
||||
cmd := exec.Command("docker", "ps")
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Dir = a.installPath
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err == nil {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "checked result: no need sudo permission\n")
|
||||
}
|
||||
a.runSudoState = NoSudo
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// check sudo docker ps command
|
||||
cmd2 := exec.Command("sudo", "-S", "docker", "ps")
|
||||
var out2 bytes.Buffer
|
||||
var stderr2 bytes.Buffer
|
||||
cmd2.Stdout = &out2
|
||||
cmd2.Stderr = &stderr2
|
||||
cmd2.Dir = a.installPath
|
||||
stdin, _ := cmd2.StdinPipe()
|
||||
defer stdin.Close()
|
||||
|
||||
if err := cmd2.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done2 := make(chan error, 1)
|
||||
go func() {
|
||||
done2 <- cmd2.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
cmd2.Process.Signal(os.Interrupt)
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "checked result: timeout execeed and need sudo with password\n")
|
||||
}
|
||||
a.runSudoState = SudoWithPassword
|
||||
|
||||
case err := <-done2:
|
||||
if err == nil {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "checked result: need sudo without password\n")
|
||||
}
|
||||
a.runSudoState = SudoWithoutPassword
|
||||
} else {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "checked result: need sudo with password\n")
|
||||
}
|
||||
a.runSudoState = SudoWithPassword
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) Install() error {
|
||||
a.checkSudoPermission()
|
||||
if a.runSudoState == SudoWithPassword {
|
||||
if !a.promptSudo() {
|
||||
return errors.New("cancel installation")
|
||||
}
|
||||
}
|
||||
|
||||
if a.hasConfigured() {
|
||||
a.Reset()
|
||||
}
|
||||
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Starting to install higress.. \n")
|
||||
}
|
||||
args := []string{"./higress"}
|
||||
args = append(args, a.profileArgs()...)
|
||||
return a.run(a.installBinaryName, args, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) Uninstall() error {
|
||||
a.checkSudoPermission()
|
||||
if a.runSudoState == SudoWithPassword {
|
||||
if !a.promptSudo() {
|
||||
return errors.New("cancel uninstall")
|
||||
}
|
||||
}
|
||||
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Starting to uninstall higress... \n")
|
||||
}
|
||||
|
||||
if err := a.Reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) Upgrade() error {
|
||||
a.checkSudoPermission()
|
||||
if a.runSudoState == SudoWithPassword {
|
||||
if !a.promptSudo() {
|
||||
return errors.New("cancel upgrade")
|
||||
}
|
||||
}
|
||||
|
||||
currentVersion := ""
|
||||
newVersion := ""
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Checking current higress version... ")
|
||||
currentVersion, _ = a.Version()
|
||||
fmt.Fprintf(a.writer, "%s\n", currentVersion)
|
||||
}
|
||||
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Starting to upgrade higress... \n")
|
||||
}
|
||||
|
||||
if err := a.run(a.installBinaryName, []string{"-u"}, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Checking new higress version... ")
|
||||
newVersion, _ = a.Version()
|
||||
fmt.Fprintf(a.writer, "%s\n", newVersion)
|
||||
}
|
||||
|
||||
if currentVersion == newVersion {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !a.promptRestart() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := a.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.Startup(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) Version() (string, error) {
|
||||
version := ""
|
||||
content, err := os.ReadFile(a.versionPath)
|
||||
if err != nil {
|
||||
return version, nil
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (a *Agent) promptSudo() bool {
|
||||
answer := ""
|
||||
for {
|
||||
fmt.Fprintf(a.writer, "\nThis need sudo permission and input root password to continue installation, Proceed? (y/N)")
|
||||
fmt.Scanln(&answer)
|
||||
if strings.TrimSpace(answer) == "y" {
|
||||
fmt.Fprintf(a.writer, "\n")
|
||||
return true
|
||||
}
|
||||
if strings.TrimSpace(answer) == "N" {
|
||||
fmt.Fprintf(a.writer, "Cancelled.\n")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) promptRestart() bool {
|
||||
answer := ""
|
||||
for {
|
||||
fmt.Fprintf(a.writer, "\nThis need to restart higress, Proceed? (y/N)")
|
||||
fmt.Scanln(&answer)
|
||||
if strings.TrimSpace(answer) == "y" {
|
||||
fmt.Fprintf(a.writer, "\n")
|
||||
return true
|
||||
}
|
||||
if strings.TrimSpace(answer) == "N" {
|
||||
fmt.Fprintf(a.writer, "Cancelled.\n")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) Startup() error {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Starting higress... \n")
|
||||
}
|
||||
return a.run(a.startupBinaryName, []string{}, true)
|
||||
}
|
||||
|
||||
func (a *Agent) Shutdown() error {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Shutdowning higress... \n")
|
||||
}
|
||||
return a.run(a.shutdownBinaryName, []string{}, true)
|
||||
}
|
||||
|
||||
func (a *Agent) Reset() error {
|
||||
if !a.quiet {
|
||||
fmt.Fprintf(a.writer, "\n⌛️ Resetting higress....\n")
|
||||
}
|
||||
return a.run(a.resetBinaryName, []string{}, true)
|
||||
}
|
||||
|
||||
func (a *Agent) hasConfigured() bool {
|
||||
if _, err := os.Stat(a.configuredPath); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -59,12 +59,15 @@ func newManifestCmd() *cobra.Command {
|
||||
|
||||
generate := newManifestGenerateCmd(iArgs)
|
||||
addManifestFlags(generate, iArgs)
|
||||
flags := generate.Flags()
|
||||
options.AddKubeConfigFlags(flags)
|
||||
manifestCmd.AddCommand(generate)
|
||||
|
||||
return manifestCmd
|
||||
}
|
||||
|
||||
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().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)
|
||||
}
|
||||
|
||||
op, err := installer.NewInstaller(profile, cliClient, writer, true)
|
||||
op, err := installer.NewK8sInstaller(profile, cliClient, writer, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
11763
pkg/cmd/hgctl/manifests/gatewayapi/experimental-install.yaml
Normal file
11763
pkg/cmd/hgctl/manifests/gatewayapi/experimental-install.yaml
Normal file
File diff suppressed because it is too large
Load Diff
10
pkg/cmd/hgctl/manifests/istiobase/Chart.yaml
Normal file
10
pkg/cmd/hgctl/manifests/istiobase/Chart.yaml
Normal 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
|
||||
21
pkg/cmd/hgctl/manifests/istiobase/README.md
Normal file
21
pkg/cmd/hgctl/manifests/istiobase/README.md
Normal 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
|
||||
```
|
||||
7199
pkg/cmd/hgctl/manifests/istiobase/crds/crd-all.gen.yaml
Normal file
7199
pkg/cmd/hgctl/manifests/istiobase/crds/crd-all.gen.yaml
Normal file
File diff suppressed because it is too large
Load Diff
48
pkg/cmd/hgctl/manifests/istiobase/crds/crd-operator.yaml
Normal file
48
pkg/cmd/hgctl/manifests/istiobase/crds/crd-operator.yaml
Normal 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
|
||||
---
|
||||
5
pkg/cmd/hgctl/manifests/istiobase/templates/NOTES.txt
Normal file
5
pkg/cmd/hgctl/manifests/istiobase/templates/NOTES.txt
Normal 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 }}
|
||||
181
pkg/cmd/hgctl/manifests/istiobase/templates/clusterrole.yaml
Normal file
181
pkg/cmd/hgctl/manifests/istiobase/templates/clusterrole.yaml
Normal 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}}
|
||||
---
|
||||
@@ -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 }}
|
||||
---
|
||||
4
pkg/cmd/hgctl/manifests/istiobase/templates/crds.yaml
Normal file
4
pkg/cmd/hgctl/manifests/istiobase/templates/crds.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
{{- if .Values.base.enableCRDTemplates }}
|
||||
{{ .Files.Get "crds/crd-all.gen.yaml" }}
|
||||
{{ .Files.Get "crds/crd-operator.yaml" }}
|
||||
{{- end }}
|
||||
48
pkg/cmd/hgctl/manifests/istiobase/templates/default.yaml
Normal file
48
pkg/cmd/hgctl/manifests/istiobase/templates/default.yaml
Normal 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 }}
|
||||
23
pkg/cmd/hgctl/manifests/istiobase/templates/endpoints.yaml
Normal file
23
pkg/cmd/hgctl/manifests/istiobase/templates/endpoints.yaml
Normal 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 }}
|
||||
@@ -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 }}
|
||||
25
pkg/cmd/hgctl/manifests/istiobase/templates/role.yaml
Normal file
25
pkg/cmd/hgctl/manifests/istiobase/templates/role.yaml
Normal 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"]
|
||||
21
pkg/cmd/hgctl/manifests/istiobase/templates/rolebinding.yaml
Normal file
21
pkg/cmd/hgctl/manifests/istiobase/templates/rolebinding.yaml
Normal 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 }}
|
||||
@@ -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 }}
|
||||
28
pkg/cmd/hgctl/manifests/istiobase/templates/services.yaml
Normal file
28
pkg/cmd/hgctl/manifests/istiobase/templates/services.yaml
Normal 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 }}
|
||||
29
pkg/cmd/hgctl/manifests/istiobase/values.yaml
Normal file
29
pkg/cmd/hgctl/manifests/istiobase/values.yaml
Normal 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"
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
// FS embeds the manifests
|
||||
//
|
||||
//go:embed profiles/*
|
||||
//go:embed gatewayapi/*
|
||||
//go:embed istiobase/*
|
||||
var FS embed.FS
|
||||
|
||||
// BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in
|
||||
|
||||
@@ -1,38 +1,16 @@
|
||||
# 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: kind
|
||||
profile: all
|
||||
global:
|
||||
install: local # install mode k8s/local/docker
|
||||
install: k8s # install mode k8s/local-k8s/local-docker/local
|
||||
ingressClass: higress
|
||||
watchNamespace:
|
||||
disableAlpnH2: true
|
||||
enableStatus: true
|
||||
enableIstioAPI: true
|
||||
enableGatewayAPI: false
|
||||
namespace: higress-system
|
||||
istioNamespace: istio-system
|
||||
|
||||
console:
|
||||
port: 8080
|
||||
replicas: 1
|
||||
serviceType: ClusterIP
|
||||
domain: console.higress.io
|
||||
tlsSecretName:
|
||||
webLoginPrompt:
|
||||
adminPasswordValue: admin
|
||||
adminPasswordLength: 8
|
||||
o11yEnabled: false
|
||||
pvcRwxSupported: true
|
||||
|
||||
gateway:
|
||||
replicas: 1
|
||||
@@ -44,7 +22,7 @@ controller:
|
||||
replicas: 1
|
||||
|
||||
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
|
||||
username:
|
||||
password:
|
||||
@@ -59,7 +37,7 @@ charts:
|
||||
url: https://higress.io/helm-charts
|
||||
name: higress
|
||||
version: latest
|
||||
istio:
|
||||
url: https://istio-release.storage.googleapis.com/charts
|
||||
name: base
|
||||
version: 1.18.2
|
||||
standalone:
|
||||
url: https://higress.io/standalone/get-higress.sh
|
||||
name: standalone
|
||||
version: latest
|
||||
@@ -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
|
||||
global:
|
||||
install: k8s # install mode k8s/local-k8s/local-docker/local
|
||||
ingressClass: higress
|
||||
watchNamespace:
|
||||
disableAlpnH2: true
|
||||
enableStatus: true
|
||||
enableIstioAPI: false
|
||||
enableGatewayAPI: false
|
||||
namespace: higress-system
|
||||
istioNamespace: istio-system
|
||||
|
||||
console:
|
||||
replicas: 1
|
||||
serviceType: ClusterIP
|
||||
domain: console.higress.io
|
||||
tlsSecretName:
|
||||
webLoginPrompt:
|
||||
adminPasswordValue: admin
|
||||
adminPasswordLength: 8
|
||||
o11yEnabled: false
|
||||
pvcRwxSupported: true
|
||||
|
||||
gateway:
|
||||
replicas: 2
|
||||
@@ -47,7 +25,7 @@ charts:
|
||||
url: https://higress.io/helm-charts
|
||||
name: higress
|
||||
version: latest
|
||||
istio:
|
||||
url: https://istio-release.storage.googleapis.com/charts
|
||||
name: base
|
||||
version: 1.18.2
|
||||
standalone:
|
||||
url: https://higress.io/standalone/get-higress.sh
|
||||
name: standalone
|
||||
version: latest
|
||||
|
||||
32
pkg/cmd/hgctl/manifests/profiles/local-docker.yaml
Normal file
32
pkg/cmd/hgctl/manifests/profiles/local-docker.yaml
Normal 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
|
||||
|
||||
@@ -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
|
||||
global:
|
||||
install: local-k8s # install mode k8s/local-k8s/local-docker/local
|
||||
ingressClass: higress
|
||||
watchNamespace:
|
||||
disableAlpnH2: true
|
||||
enableStatus: true
|
||||
enableIstioAPI: true
|
||||
enableGatewayAPI: true
|
||||
namespace: higress-system
|
||||
istioNamespace: istio-system
|
||||
|
||||
console:
|
||||
replicas: 1
|
||||
serviceType: ClusterIP
|
||||
domain: console.higress.io
|
||||
tlsSecretName:
|
||||
webLoginPrompt:
|
||||
adminPasswordValue: admin
|
||||
adminPasswordLength: 8
|
||||
o11yEnabled: true
|
||||
pvcRwxSupported: true
|
||||
|
||||
gateway:
|
||||
replicas: 1
|
||||
@@ -47,7 +25,7 @@ charts:
|
||||
url: https://higress.io/helm-charts
|
||||
name: higress
|
||||
version: latest
|
||||
istio:
|
||||
url: https://istio-release.storage.googleapis.com/charts
|
||||
name: base
|
||||
version: 1.18.2
|
||||
standalone:
|
||||
url: https://higress.io/standalone/get-higress.sh
|
||||
name: standalone
|
||||
version: latest
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
@@ -29,18 +28,10 @@ import (
|
||||
type uninstallArgs struct {
|
||||
// purgeIstioCRD delete all of Istio resources.
|
||||
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) {
|
||||
cmd.PersistentFlags().StringVar(&args.istioNamespace, "istio-namespace", "istio-system",
|
||||
"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,
|
||||
cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "", false,
|
||||
"Delete all of Istio resources")
|
||||
}
|
||||
|
||||
@@ -50,15 +41,13 @@ func newUninstallCmd() *cobra.Command {
|
||||
uninstallCmd := &cobra.Command{
|
||||
Use: "uninstall",
|
||||
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
|
||||
hgctl uninstall
|
||||
|
||||
# Uninstall higress by special namespace
|
||||
hgctl uninstall --namespace=higress-system
|
||||
hgctl uninstal
|
||||
|
||||
# Uninstall higress and istio CRD
|
||||
hgctl uninstall --purge-istio-crd --istio-namespace=istio-system`,
|
||||
# Uninstall higress and istio CRD from a cluster
|
||||
hgctl uninstall --purge-istio-crd
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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.
|
||||
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)
|
||||
profileName := helm.GetUninstallProfileName()
|
||||
_, profile, err := helm.GenProfile(profileName, "", setFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
|
||||
err = profile.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !promptUninstall(writer) {
|
||||
return nil
|
||||
}
|
||||
|
||||
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
|
||||
profile.Global.Namespace = uiArgs.namespace
|
||||
profile.Global.IstioNamespace = uiArgs.istioNamespace
|
||||
err = UnInstallManifests(profile, writer)
|
||||
if profile.Global.Install == helm.InstallK8s || profile.Global.Install == helm.InstallLocalK8s {
|
||||
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
|
||||
}
|
||||
|
||||
err = uninstallManifests(profile, writer, uiArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -108,29 +109,16 @@ func promptUninstall(writer io.Writer) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func UnInstallManifests(profile *helm.Profile, writer io.Writer) error {
|
||||
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||
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()
|
||||
func uninstallManifests(profile *helm.Profile, writer io.Writer, uiArgs *uninstallArgs) error {
|
||||
installer, err := installer.NewInstaller(profile, writer, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "\n⌛️ Processing uninstallation... \n\n")
|
||||
if err := op.DeleteManifests(manifestMap); err != nil {
|
||||
err = installer.UnInstall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(writer, "\n🎊 Uninstall All Resources Complete!\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
package hgctl
|
||||
|
||||
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/spf13/cobra"
|
||||
)
|
||||
@@ -24,6 +30,7 @@ type upgradeArgs struct {
|
||||
}
|
||||
|
||||
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().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" +
|
||||
" that performs additional upgrade-related checks.",
|
||||
RunE: func(cmd *cobra.Command, args []string) (e error) {
|
||||
return Install(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
|
||||
return upgrade(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
|
||||
},
|
||||
}
|
||||
addUpgradeFlags(upgradeCmd, upgradeArgs)
|
||||
@@ -47,3 +54,70 @@ func newUpgradeCmd() *cobra.Command {
|
||||
options.AddKubeConfigFlags(flags)
|
||||
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
|
||||
}
|
||||
|
||||
123
pkg/cmd/hgctl/util/http_fetcher.go
Normal file
123
pkg/cmd/hgctl/util/http_fetcher.go
Normal 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)
|
||||
}
|
||||
@@ -15,8 +15,10 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -76,3 +78,18 @@ func ParseValue(valueStr string) any {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user