mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 12:47:28 +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/'
|
- 'tools/'
|
||||||
- 'test/README.md'
|
- 'test/README.md'
|
||||||
- 'pkg/cmd/hgctl/testdata/config'
|
- 'pkg/cmd/hgctl/testdata/config'
|
||||||
|
- 'pkg/cmd/hgctl/manifests'
|
||||||
|
|
||||||
comment: on-failure
|
comment: on-failure
|
||||||
dependency:
|
dependency:
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -172,6 +172,8 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.15.9 // indirect
|
github.com/klauspost/compress v1.15.9 // indirect
|
||||||
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
|
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
|
||||||
@@ -225,6 +227,7 @@ require (
|
|||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
github.com/prometheus/statsd_exporter v0.21.0 // indirect
|
github.com/prometheus/statsd_exporter v0.21.0 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 // indirect
|
||||||
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
|
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
|
||||||
github.com/russross/blackfriday v1.6.0 // indirect
|
github.com/russross/blackfriday v1.6.0 // indirect
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect
|
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ func newDashboardCmd() *cobra.Command {
|
|||||||
controllerDebugCmd := controllerDebugCmd()
|
controllerDebugCmd := controllerDebugCmd()
|
||||||
controllerDebugCmd.PersistentFlags().IntVar(&controllerPort, "ui-port", defaultControllerPort, "The component dashboard UI port.")
|
controllerDebugCmd.PersistentFlags().IntVar(&controllerPort, "ui-port", defaultControllerPort, "The component dashboard UI port.")
|
||||||
dashboardCmd.AddCommand(controllerDebugCmd)
|
dashboardCmd.AddCommand(controllerDebugCmd)
|
||||||
|
flags := dashboardCmd.PersistentFlags()
|
||||||
|
options.AddKubeConfigFlags(flags)
|
||||||
return dashboardCmd
|
return dashboardCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,40 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadYamlProfile gets the overlay yaml file from list of files and return profile value from file overlay and set overlay.
|
// GetProfileFromFlags get profile name from flags.
|
||||||
func ReadYamlProfile(inFilenames []string, setFlags []string) (string, string, error) {
|
func GetProfileFromFlags(setFlags []string) (string, error) {
|
||||||
profileName := DefaultProfileName
|
profileName := DefaultProfileName
|
||||||
// The profile coming from --set flag has the highest precedence.
|
// The profile coming from --set flag has the highest precedence.
|
||||||
psf := GetValueForSetFlag(setFlags, "profile")
|
psf := GetValueForSetFlag(setFlags, "profile")
|
||||||
if psf != "" {
|
if psf != "" {
|
||||||
profileName = psf
|
profileName = psf
|
||||||
}
|
}
|
||||||
return "", profileName, nil
|
return profileName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValuesOverylayFromFiles(inFilenames []string) (string, error) {
|
||||||
|
// Convert layeredYamls under values node in profile file to support helm values
|
||||||
|
overLayYamls := ""
|
||||||
|
// Get Overlays from files
|
||||||
|
if len(inFilenames) > 0 {
|
||||||
|
layeredYamls, err := ReadLayeredYAMLs(inFilenames)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
vals := make(map[string]any)
|
||||||
|
if err := yaml.Unmarshal([]byte(layeredYamls), &vals); err != nil {
|
||||||
|
return "", fmt.Errorf("%s:\n\nYAML:\n%s", err, layeredYamls)
|
||||||
|
}
|
||||||
|
values := make(map[string]any)
|
||||||
|
values["values"] = vals
|
||||||
|
out, err := yaml.Marshal(values)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
overLayYamls = string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return overLayYamls, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUninstallProfileName() string {
|
func GetUninstallProfileName() string {
|
||||||
@@ -101,12 +126,17 @@ func GenerateConfig(inFilenames []string, setFlags []string) (string, *Profile,
|
|||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
fy, profileName, err := ReadYamlProfile(inFilenames, setFlags)
|
profileName, err := GetProfileFromFlags(setFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
profileString, profile, err := GenProfile(profileName, fy, setFlags)
|
valuesOverlay, err := GetValuesOverylayFromFiles(inFilenames)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
profileString, profile, err := GenProfile(profileName, valuesOverlay, setFlags)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ package helm
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"istio.io/istio/operator/pkg/util"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,83 +45,72 @@ type Profile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProfileGlobal struct {
|
type ProfileGlobal struct {
|
||||||
Install InstallMode `json:"install,omitempty"`
|
Install InstallMode `json:"install,omitempty"`
|
||||||
IngressClass string `json:"ingressClass,omitempty"`
|
IngressClass string `json:"ingressClass,omitempty"`
|
||||||
WatchNamespace string `json:"watchNamespace,omitempty"`
|
EnableIstioAPI bool `json:"enableIstioAPI,omitempty"`
|
||||||
DisableAlpnH2 bool `json:"disableAlpnH2,omitempty"`
|
EnableGatewayAPI bool `json:"enableGatewayAPI,omitempty"`
|
||||||
EnableStatus bool `json:"enableStatus,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
EnableIstioAPI bool `json:"enableIstioAPI,omitempty"`
|
|
||||||
Namespace string `json:"namespace,omitempty"`
|
|
||||||
IstioNamespace string `json:"istioNamespace,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) {
|
func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) {
|
||||||
sets := make([]string, 0)
|
sets := make([]string, 0)
|
||||||
sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass))
|
if install == InstallK8s || install == InstallLocalK8s {
|
||||||
sets = append(sets, fmt.Sprintf("global.watchNamespace=%s", p.WatchNamespace))
|
sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass))
|
||||||
sets = append(sets, fmt.Sprintf("global.disableAlpnH2=%t", p.DisableAlpnH2))
|
sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI))
|
||||||
sets = append(sets, fmt.Sprintf("global.enableStatus=%t", p.EnableStatus))
|
sets = append(sets, fmt.Sprintf("global.enableGatewayAPI=%t", p.EnableGatewayAPI))
|
||||||
sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI))
|
if install == InstallLocalK8s {
|
||||||
sets = append(sets, fmt.Sprintf("global.istioNamespace=%s", p.IstioNamespace))
|
sets = append(sets, fmt.Sprintf("global.local=%t", true))
|
||||||
if install == InstallLocalK8s {
|
}
|
||||||
sets = append(sets, fmt.Sprintf("global.local=%t", true))
|
|
||||||
}
|
}
|
||||||
return sets, nil
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileGlobal) Validate(install InstallMode) []error {
|
func (p ProfileGlobal) Validate(install InstallMode) []error {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
// now only support k8s and local-k8s installation mode
|
// now only support k8s, local-k8s, local-docker installation mode
|
||||||
if p.Install != InstallK8s && p.Install != InstallLocalK8s {
|
if install != InstallK8s && install != InstallLocalK8s && install != InstallLocalDocker {
|
||||||
errs = append(errs, errors.New("global.install only can be set to k8s or local-k8s"))
|
errs = append(errs, errors.New("global.install only can be set to k8s, local-k8s or local-docker"))
|
||||||
}
|
}
|
||||||
if len(p.IngressClass) == 0 {
|
if install == InstallK8s || install == InstallLocalK8s {
|
||||||
errs = append(errs, errors.New("global.ingressClass can't be empty"))
|
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.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"))
|
|
||||||
}
|
}
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileConsole struct {
|
type ProfileConsole struct {
|
||||||
Port uint32 `json:"port,omitempty"`
|
Port uint32 `json:"port,omitempty"`
|
||||||
Replicas uint32 `json:"replicas,omitempty"`
|
Replicas uint32 `json:"replicas,omitempty"`
|
||||||
ServiceType string `json:"serviceType,omitempty"`
|
AdminPasswordValue string `json:"adminPasswordValue,omitempty"`
|
||||||
Domain string `json:"domain,omitempty"`
|
O11yEnabled bool `json:"o11YEnabled,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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) {
|
func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) {
|
||||||
sets := make([]string, 0)
|
sets := make([]string, 0)
|
||||||
sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas))
|
if install == InstallK8s || install == InstallLocalK8s {
|
||||||
sets = append(sets, fmt.Sprintf("higress-console.service.type=%s", p.ServiceType))
|
sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas))
|
||||||
sets = append(sets, fmt.Sprintf("higress-console.domain=%s", p.Domain))
|
sets = append(sets, fmt.Sprintf("higress-console.admin.password.value=%s", p.AdminPasswordValue))
|
||||||
sets = append(sets, fmt.Sprintf("higress-console.tlsSecretName=%s", p.TlsSecretName))
|
sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled))
|
||||||
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))
|
|
||||||
return sets, nil
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileConsole) Validate(install InstallMode) []error {
|
func (p ProfileConsole) Validate(install InstallMode) []error {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
if p.Replicas <= 0 {
|
if install == InstallK8s || install == InstallLocalK8s {
|
||||||
errs = append(errs, errors.New("console.replica need be large than zero"))
|
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" {
|
if install == InstallLocalDocker {
|
||||||
errs = append(errs, errors.New("console.serviceType can only be set to ClusterIP, NodePort or LoadBalancer"))
|
if p.Port <= 0 {
|
||||||
|
errs = append(errs, errors.New("console.port need be large than zero"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
@@ -134,16 +125,31 @@ type ProfileGateway struct {
|
|||||||
|
|
||||||
func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) {
|
func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) {
|
||||||
sets := make([]string, 0)
|
sets := make([]string, 0)
|
||||||
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
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileGateway) Validate(install InstallMode) []error {
|
func (p ProfileGateway) Validate(install InstallMode) []error {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
if p.Replicas <= 0 {
|
if install == InstallK8s || install == InstallLocalK8s {
|
||||||
errs = append(errs, errors.New("gateway.replica need be large than zero"))
|
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
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,16 +159,19 @@ type ProfileController struct {
|
|||||||
|
|
||||||
func (p ProfileController) SetFlags(install InstallMode) ([]string, error) {
|
func (p ProfileController) SetFlags(install InstallMode) ([]string, error) {
|
||||||
sets := make([]string, 0)
|
sets := make([]string, 0)
|
||||||
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
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileController) Validate(install InstallMode) []error {
|
func (p ProfileController) Validate(install InstallMode) []error {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
if p.Replicas <= 0 {
|
if install == InstallK8s || install == InstallLocalK8s {
|
||||||
errs = append(errs, errors.New("controller.replica need be large than zero"))
|
if p.Replicas <= 0 {
|
||||||
|
errs = append(errs, errors.New("controller.replica need be large than zero"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +185,31 @@ type ProfileStorage struct {
|
|||||||
|
|
||||||
func (p ProfileStorage) Validate(install InstallMode) []error {
|
func (p ProfileStorage) Validate(install InstallMode) []error {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
|
if install == InstallLocalDocker {
|
||||||
|
if len(p.Url) == 0 {
|
||||||
|
errs = append(errs, errors.New("storage.url can't be empty"))
|
||||||
|
}
|
||||||
|
if len(p.Ns) == 0 {
|
||||||
|
errs = append(errs, errors.New("storage.ns can't be empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(p.Url, "nacos://") && !strings.HasPrefix(p.Url, "file://") {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid storage url: %s", p.Url))
|
||||||
|
} else {
|
||||||
|
// check localhost or 127.0.0.0
|
||||||
|
if strings.Contains(p.Url, "localhost") || strings.Contains(p.Url, "/127.") {
|
||||||
|
errs = append(errs, errors.New("localhost or loopback addresses in nacos url won't work"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.DataEncKey) > 0 && len(p.DataEncKey) != 32 {
|
||||||
|
errs = append(errs, fmt.Errorf("expecting 32 characters for dataEncKey, but got %d length", len(p.DataEncKey)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Username) > 0 && len(p.Password) == 0 || len(p.Username) == 0 && len(p.Password) > 0 {
|
||||||
|
errs = append(errs, errors.New("both nacos username and password should be provided"))
|
||||||
|
}
|
||||||
|
}
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +220,8 @@ type Chart struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProfileCharts struct {
|
type ProfileCharts struct {
|
||||||
Higress Chart `json:"higress,omitempty"`
|
Higress Chart `json:"higress,omitempty"`
|
||||||
Istio Chart `json:"istio,omitempty"`
|
Standalone Chart `json:"standalone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProfileCharts) Validate(install InstallMode) []error {
|
func (p ProfileCharts) Validate(install InstallMode) []error {
|
||||||
@@ -222,8 +256,13 @@ func (p *Profile) ValuesYaml() (string, error) {
|
|||||||
}
|
}
|
||||||
valueOverlayYAML = string(out)
|
valueOverlayYAML = string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flagsYAML, err := overlaySetFlagValues("", setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
// merge values and setFlags
|
// merge values and setFlags
|
||||||
overlayYAML, err := overlaySetFlagValues(valueOverlayYAML, setFlags)
|
overlayYAML, err := util.OverlayYAML(flagsYAML, valueOverlayYAML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -237,6 +276,26 @@ func (p *Profile) IstioEnabled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Profile) GatewayAPIEnabled() bool {
|
||||||
|
if (p.Global.Install == InstallK8s || p.Global.Install == InstallLocalK8s) && p.Global.EnableGatewayAPI {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Profile) GetIstioNamespace() string {
|
||||||
|
if valuesGlobal, ok1 := p.Values["global"]; ok1 {
|
||||||
|
if global, ok2 := valuesGlobal.(map[string]any); ok2 {
|
||||||
|
if istioNamespace, ok3 := global["istioNamespace"]; ok3 {
|
||||||
|
if namespace, ok4 := istioNamespace.(string); ok4 {
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Profile) Validate() error {
|
func (p *Profile) Validate() error {
|
||||||
errs := make([]error, 0)
|
errs := make([]error, 0)
|
||||||
errsGlobal := p.Global.Validate(p.Global.Install)
|
errsGlobal := p.Global.Validate(p.Global.Install)
|
||||||
@@ -256,7 +315,7 @@ func (p *Profile) Validate() error {
|
|||||||
errs = append(errs, errsController...)
|
errs = append(errs, errsController...)
|
||||||
}
|
}
|
||||||
errsStorage := p.Storage.Validate(p.Global.Install)
|
errsStorage := p.Storage.Validate(p.Global.Install)
|
||||||
if len(errsController) > 0 {
|
if len(errsStorage) > 0 {
|
||||||
errs = append(errs, errsStorage...)
|
errs = append(errs, errsStorage...)
|
||||||
}
|
}
|
||||||
errsCharts := p.Charts.Validate(p.Global.Install)
|
errsCharts := p.Charts.Validate(p.Global.Install)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ type RendererOptions struct {
|
|||||||
Name string
|
Name string
|
||||||
Namespace string
|
Namespace string
|
||||||
|
|
||||||
// fields for LocalRenderer
|
// fields for LocalChartRenderer and LocalFileRenderer
|
||||||
FS fs.FS
|
FS fs.FS
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
@@ -174,14 +174,84 @@ func WithRepoURL(repo string) RendererOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalRenderer load chart from local file system
|
// LocalFileRenderer load yaml files from local file system
|
||||||
type LocalRenderer struct {
|
type LocalFileRenderer struct {
|
||||||
|
Opts *RendererOptions
|
||||||
|
filesMap map[string]string
|
||||||
|
Started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocalFileRenderer(opts ...RendererOption) (Renderer, error) {
|
||||||
|
newOpts := &RendererOptions{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(newOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LocalFileRenderer{
|
||||||
|
Opts: newOpts,
|
||||||
|
filesMap: make(map[string]string),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileRenderer) Init() error {
|
||||||
|
fileNames, err := getFileNames(l.Opts.FS, l.Opts.Dir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("chart of component %s doesn't exist", l.Opts.Name)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("getFileNames err: %s", err)
|
||||||
|
}
|
||||||
|
for _, fileName := range fileNames {
|
||||||
|
data, err := fs.ReadFile(l.Opts.FS, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ReadFile %s err: %s", fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.filesMap[fileName] = string(data)
|
||||||
|
}
|
||||||
|
l.Started = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileRenderer) RenderManifest(valsYaml string) (string, error) {
|
||||||
|
if !l.Started {
|
||||||
|
return "", errors.New("LocalFileRenderer has not been init")
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(l.filesMap))
|
||||||
|
for key := range l.filesMap {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
// to ensure that every manifest rendered by same values are the same
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
file := l.filesMap[keys[i]]
|
||||||
|
file = util.ApplyFilters(file, DefaultFilters...)
|
||||||
|
// ignore empty manifest
|
||||||
|
if file == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(file, YAMLSeparator) {
|
||||||
|
file += YAMLSeparator
|
||||||
|
}
|
||||||
|
builder.WriteString(file)
|
||||||
|
}
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileRenderer) SetVersion(version string) {
|
||||||
|
l.Opts.Version = version
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalChartRenderer load chart from local file system
|
||||||
|
type LocalChartRenderer struct {
|
||||||
Opts *RendererOptions
|
Opts *RendererOptions
|
||||||
Chart *chart.Chart
|
Chart *chart.Chart
|
||||||
Started bool
|
Started bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *LocalRenderer) Init() error {
|
func (lr *LocalChartRenderer) Init() error {
|
||||||
fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir)
|
fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -212,18 +282,18 @@ func (lr *LocalRenderer) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *LocalRenderer) RenderManifest(valsYaml string) (string, error) {
|
func (lr *LocalChartRenderer) RenderManifest(valsYaml string) (string, error) {
|
||||||
if !lr.Started {
|
if !lr.Started {
|
||||||
return "", errors.New("LocalRenderer has not been init")
|
return "", errors.New("LocalChartRenderer has not been init")
|
||||||
}
|
}
|
||||||
return renderManifest(valsYaml, lr.Chart, true, lr.Opts, DefaultFilters...)
|
return renderManifest(valsYaml, lr.Chart, true, lr.Opts, DefaultFilters...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *LocalRenderer) SetVersion(version string) {
|
func (lr *LocalChartRenderer) SetVersion(version string) {
|
||||||
lr.Opts.Version = version
|
lr.Opts.Version = version
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
|
func NewLocalChartRenderer(opts ...RendererOption) (Renderer, error) {
|
||||||
newOpts := &RendererOptions{}
|
newOpts := &RendererOptions{}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(newOpts)
|
opt(newOpts)
|
||||||
@@ -232,7 +302,7 @@ func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
|
|||||||
if err := verifyRendererOptions(newOpts); err != nil {
|
if err := verifyRendererOptions(newOpts); err != nil {
|
||||||
return nil, fmt.Errorf("verify err: %s", err)
|
return nil, fmt.Errorf("verify err: %s", err)
|
||||||
}
|
}
|
||||||
return &LocalRenderer{
|
return &LocalChartRenderer{
|
||||||
Opts: newOpts,
|
Opts: newOpts,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/options"
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -32,13 +31,16 @@ const (
|
|||||||
// manifestsFlagHelpStr is the command line description for --manifests
|
// manifestsFlagHelpStr is the command line description for --manifests
|
||||||
manifestsFlagHelpStr = `Specify a path to a directory of profiles
|
manifestsFlagHelpStr = `Specify a path to a directory of profiles
|
||||||
(e.g. ~/Downloads/higress/manifests).`
|
(e.g. ~/Downloads/higress/manifests).`
|
||||||
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"
|
profileNameK8s = "k8s"
|
||||||
profileNameLocalK8s = "local-k8s"
|
profileNameLocalK8s = "local-k8s"
|
||||||
|
profileNameLocalDocker = "local-docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InstallArgs struct {
|
type InstallArgs struct {
|
||||||
|
// InFilenames is a filename to helm custom values
|
||||||
InFilenames []string
|
InFilenames []string
|
||||||
// KubeConfigPath is the path to kube config file.
|
// KubeConfigPath is the path to kube config file.
|
||||||
KubeConfigPath string
|
KubeConfigPath string
|
||||||
@@ -61,6 +63,7 @@ func (a *InstallArgs) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addInstallFlags(cmd *cobra.Command, args *InstallArgs) {
|
func addInstallFlags(cmd *cobra.Command, args *InstallArgs) {
|
||||||
|
cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
|
||||||
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
||||||
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
}
|
}
|
||||||
@@ -87,19 +90,23 @@ func newInstallCmd() *cobra.Command {
|
|||||||
# Install higress on local kubernetes cluster
|
# Install higress on local kubernetes cluster
|
||||||
hgctl install --set profile=local-k8s
|
hgctl install --set profile=local-k8s
|
||||||
|
|
||||||
|
# Install higress on local docker environment with specific gateway port
|
||||||
|
hgctl install --set profile=local-docker --set gateway.httpPort=80 --set gateway.httpsPort=443
|
||||||
|
|
||||||
# To override profile setting
|
# To override profile setting
|
||||||
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
|
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
|
||||||
|
|
||||||
# To override helm setting
|
# To override helm setting
|
||||||
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m"
|
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m"
|
||||||
|
|
||||||
|
|
||||||
`,
|
`,
|
||||||
Args: cobra.ExactArgs(0),
|
Args: cobra.ExactArgs(0),
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return Install(cmd.OutOrStdout(), iArgs)
|
return install(cmd.OutOrStdout(), iArgs)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
addInstallFlags(installCmd, iArgs)
|
addInstallFlags(installCmd, iArgs)
|
||||||
@@ -108,7 +115,7 @@ func newInstallCmd() *cobra.Command {
|
|||||||
return installCmd
|
return installCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func Install(writer io.Writer, iArgs *InstallArgs) error {
|
func install(writer io.Writer, iArgs *InstallArgs) error {
|
||||||
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
|
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
|
||||||
|
|
||||||
// check profileName
|
// check profileName
|
||||||
@@ -133,7 +140,7 @@ func Install(writer io.Writer, iArgs *InstallArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = InstallManifests(profile, writer)
|
err = installManifests(profile, writer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to install manifests: %v", err)
|
return fmt.Errorf("failed to install manifests: %v", err)
|
||||||
}
|
}
|
||||||
@@ -158,11 +165,12 @@ func promptInstall(writer io.Writer, profileName string) bool {
|
|||||||
|
|
||||||
func promptProfileName(writer io.Writer) string {
|
func promptProfileName(writer io.Writer) string {
|
||||||
answer := ""
|
answer := ""
|
||||||
fmt.Fprintf(writer, "Please select higress install configration profile:\n")
|
fmt.Fprintf(writer, "\nPlease select higress install configration profile:\n")
|
||||||
fmt.Fprintf(writer, "1.Install higress to local kubernetes cluster like kind etc.\n")
|
fmt.Fprintf(writer, "\n1.Install higress to local kubernetes cluster like kind etc.\n")
|
||||||
fmt.Fprintf(writer, "2.Install higress to kubernetes cluster\n")
|
fmt.Fprintf(writer, "\n2.Install higress to kubernetes cluster\n")
|
||||||
|
fmt.Fprintf(writer, "\n3.Install higress to local docker environment\n")
|
||||||
for {
|
for {
|
||||||
fmt.Fprintf(writer, "Please input 1 or 2 to select, input your selection:")
|
fmt.Fprintf(writer, "\nPlease input 1, 2 or 3 to select, input your selection:")
|
||||||
fmt.Scanln(&answer)
|
fmt.Scanln(&answer)
|
||||||
if strings.TrimSpace(answer) == "1" {
|
if strings.TrimSpace(answer) == "1" {
|
||||||
return profileNameLocalK8s
|
return profileNameLocalK8s
|
||||||
@@ -170,33 +178,23 @@ func promptProfileName(writer io.Writer) string {
|
|||||||
if strings.TrimSpace(answer) == "2" {
|
if strings.TrimSpace(answer) == "2" {
|
||||||
return profileNameK8s
|
return profileNameK8s
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(answer) == "3" {
|
||||||
|
return profileNameLocalDocker
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InstallManifests(profile *helm.Profile, writer io.Writer) error {
|
func installManifests(profile *helm.Profile, writer io.Writer) error {
|
||||||
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
installer, err := installer.NewInstaller(profile, writer, false)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to build kubernetes client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
op, err := installer.NewInstaller(profile, cliClient, writer, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := op.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
manifestMap, err := op.RenderManifests()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(writer, "\n⌛️ Processing installation... \n\n")
|
err = installer.Install()
|
||||||
if err := op.ApplyManifests(manifestMap); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, "\n🎊 Install All Resources Complete!\n")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -96,29 +95,19 @@ func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...Compon
|
|||||||
opt(newOpts)
|
opt(newOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
var renderer helm.Renderer
|
if len(newOpts.RepoURL) == 0 {
|
||||||
var err error
|
return nil, errors.New("Higress helm chart url can't be empty")
|
||||||
if newOpts.RepoURL != "" {
|
}
|
||||||
renderer, err = helm.NewRemoteRenderer(
|
|
||||||
helm.WithName(newOpts.ChartName),
|
// Higress can only be installed by remote type
|
||||||
helm.WithNamespace(newOpts.Namespace),
|
renderer, err := helm.NewRemoteRenderer(
|
||||||
helm.WithRepoURL(newOpts.RepoURL),
|
helm.WithName(newOpts.ChartName),
|
||||||
helm.WithVersion(newOpts.Version),
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
)
|
helm.WithRepoURL(newOpts.RepoURL),
|
||||||
if err != nil {
|
helm.WithVersion(newOpts.Version),
|
||||||
return nil, err
|
)
|
||||||
}
|
if err != nil {
|
||||||
} else {
|
return nil, err
|
||||||
renderer, err = helm.NewLocalRenderer(
|
|
||||||
helm.WithName(newOpts.ChartName),
|
|
||||||
helm.WithNamespace(newOpts.Namespace),
|
|
||||||
helm.WithVersion(newOpts.Version),
|
|
||||||
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
|
||||||
helm.WithDir(string(Higress)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
higressComponent := &HigressComponent{
|
higressComponent := &HigressComponent{
|
||||||
|
|||||||
@@ -18,196 +18,113 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
|
"k8s.io/client-go/util/homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Installer struct {
|
const (
|
||||||
started bool
|
HgctlHomeDirPath = ".hgctl"
|
||||||
components map[ComponentName]Component
|
StandaloneInstalledPath = "higress-standalone"
|
||||||
kubeCli kubernetes.CLIClient
|
ProfileInstalledPath = "profiles"
|
||||||
profile *helm.Profile
|
InstalledYamlFileName = "install.yaml"
|
||||||
writer io.Writer
|
DefaultGatewayAPINamespace = "gateway-system"
|
||||||
|
DefaultIstioNamespace = "istio-system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Installer interface {
|
||||||
|
Install() error
|
||||||
|
UnInstall() error
|
||||||
|
Upgrade() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run must be invoked before invoking other functions.
|
func NewInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (Installer, error) {
|
||||||
func (o *Installer) Run() error {
|
switch profile.Global.Install {
|
||||||
for name, component := range o.components {
|
case helm.InstallK8s, helm.InstallLocalK8s:
|
||||||
if !component.Enabled() {
|
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := component.Run(); err != nil {
|
|
||||||
return fmt.Errorf("component %s run failed, err: %s", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.started = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderManifests renders component manifests specified by profile.
|
|
||||||
func (o *Installer) RenderManifests() (map[ComponentName]string, error) {
|
|
||||||
if !o.started {
|
|
||||||
return nil, errors.New("HigressOperator is not running")
|
|
||||||
}
|
|
||||||
res := make(map[ComponentName]string)
|
|
||||||
for name, component := range o.components {
|
|
||||||
if !component.Enabled() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
manifest, err := component.RenderManifest()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
|
return nil, fmt.Errorf("failed to build kubernetes client: %w", err)
|
||||||
}
|
}
|
||||||
res[name] = manifest
|
installer, err := NewK8sInstaller(profile, cliClient, writer, quiet)
|
||||||
|
return installer, err
|
||||||
|
case helm.InstallLocalDocker:
|
||||||
|
installer, err := NewDockerInstaller(profile, writer, quiet)
|
||||||
|
return installer, err
|
||||||
|
default:
|
||||||
|
return nil, errors.New("install is not supported")
|
||||||
}
|
}
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateManifests generates component manifests to k8s cluster
|
func GetHomeDir() (string, error) {
|
||||||
func (o *Installer) GenerateManifests(manifestMap map[ComponentName]string) error {
|
home := homedir.HomeDir()
|
||||||
if o.kubeCli == nil {
|
if home == "" {
|
||||||
return errors.New("no injected k8s cli into Installer")
|
return "", fmt.Errorf("No user home environment variable found for OS %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
for _, manifest := range manifestMap {
|
|
||||||
fmt.Fprint(o.writer, manifest)
|
return home, nil
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyManifests apply component manifests to k8s cluster
|
func GetHgctlPath() (string, error) {
|
||||||
func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error {
|
home, err := GetHomeDir()
|
||||||
if o.kubeCli == nil {
|
|
||||||
return errors.New("no injected k8s cli into Installer")
|
|
||||||
}
|
|
||||||
for name, manifest := range manifestMap {
|
|
||||||
namespace := o.components[name].Namespace()
|
|
||||||
if err := o.applyManifest(manifest, namespace); err != nil {
|
|
||||||
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Installer) applyManifest(manifest string, ns string) error {
|
|
||||||
if err := o.kubeCli.CreateNamespace(ns); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
for _, obj := range objs {
|
|
||||||
// check namespaced object if namespace property has been existed
|
hgctlPath := filepath.Join(home, HgctlHomeDirPath)
|
||||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
if _, err := os.Stat(hgctlPath); os.IsNotExist(err) {
|
||||||
obj.Namespace = ns
|
if err = os.MkdirAll(hgctlPath, os.ModePerm); err != nil {
|
||||||
obj.UnstructuredObject().SetNamespace(ns)
|
return "", err
|
||||||
}
|
|
||||||
if o.isNamespacedObject(obj) {
|
|
||||||
fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name)
|
|
||||||
}
|
|
||||||
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return hgctlPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteManifests delete component manifests to k8s cluster
|
func GetDefaultInstallPackagePath() (string, error) {
|
||||||
func (o *Installer) DeleteManifests(manifestMap map[ComponentName]string) error {
|
dir, err := os.Getwd()
|
||||||
if o.kubeCli == nil {
|
|
||||||
return errors.New("no injected k8s cli into Installer")
|
|
||||||
}
|
|
||||||
for name, manifest := range manifestMap {
|
|
||||||
namespace := o.components[name].Namespace()
|
|
||||||
if err := o.deleteManifest(manifest, namespace); err != nil {
|
|
||||||
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteManifest delete manifest to certain namespace
|
|
||||||
func (o *Installer) deleteManifest(manifest string, ns string) error {
|
|
||||||
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
for _, obj := range objs {
|
|
||||||
// check namespaced object if namespace property has been existed
|
path := filepath.Join(dir, StandaloneInstalledPath)
|
||||||
if obj.Namespace == "" && o.isNamespacedObject(obj) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
obj.Namespace = ns
|
if err = os.MkdirAll(path, os.ModePerm); err != nil {
|
||||||
obj.UnstructuredObject().SetNamespace(ns)
|
return "", err
|
||||||
}
|
|
||||||
if o.isNamespacedObject(obj) {
|
|
||||||
fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name)
|
|
||||||
}
|
|
||||||
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return path, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool {
|
func GetProfileInstalledPath() (string, error) {
|
||||||
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
|
hgctlPath, err := GetHgctlPath()
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*Installer, error) {
|
|
||||||
if profile == nil {
|
|
||||||
return nil, errors.New("install profile is empty")
|
|
||||||
}
|
|
||||||
// initialize components
|
|
||||||
components := make(map[ComponentName]Component)
|
|
||||||
opts := []ComponentOption{
|
|
||||||
WithComponentNamespace(profile.Global.Namespace),
|
|
||||||
WithComponentChartPath(profile.InstallPackagePath),
|
|
||||||
WithComponentVersion(profile.Charts.Higress.Version),
|
|
||||||
WithComponentRepoURL(profile.Charts.Higress.Url),
|
|
||||||
WithComponentChartName(profile.Charts.Higress.Name),
|
|
||||||
}
|
|
||||||
if quiet {
|
|
||||||
opts = append(opts, WithQuiet())
|
|
||||||
}
|
|
||||||
higressComponent, err := NewHigressComponent(profile, writer, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
|
return "", err
|
||||||
}
|
}
|
||||||
components[Higress] = higressComponent
|
|
||||||
|
|
||||||
if profile.IstioEnabled() {
|
profilesPath := filepath.Join(hgctlPath, ProfileInstalledPath)
|
||||||
opts := []ComponentOption{
|
if _, err := os.Stat(profilesPath); os.IsNotExist(err) {
|
||||||
WithComponentNamespace(profile.Global.IstioNamespace),
|
if err = os.MkdirAll(profilesPath, os.ModePerm); err != nil {
|
||||||
WithComponentChartPath(profile.InstallPackagePath),
|
return "", err
|
||||||
WithComponentVersion(profile.Charts.Istio.Version),
|
|
||||||
WithComponentRepoURL(profile.Charts.Istio.Url),
|
|
||||||
WithComponentChartName(profile.Charts.Istio.Name),
|
|
||||||
}
|
|
||||||
if quiet {
|
|
||||||
opts = append(opts, WithQuiet())
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...)
|
return profilesPath, nil
|
||||||
if err != nil {
|
}
|
||||||
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
|
|
||||||
}
|
func GetInstalledYamlPath() (string, bool) {
|
||||||
components[Istio] = istioCRDComponent
|
profileInstalledPath, err := GetProfileInstalledPath()
|
||||||
}
|
if err != nil {
|
||||||
op := &Installer{
|
return "", false
|
||||||
profile: profile,
|
}
|
||||||
components: components,
|
installedYamlFile := filepath.Join(profileInstalledPath, InstalledYamlFileName)
|
||||||
kubeCli: cli,
|
if _, err := os.Stat(installedYamlFile); os.IsNotExist(err) {
|
||||||
writer: writer,
|
return installedYamlFile, false
|
||||||
}
|
}
|
||||||
return op, nil
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"strings"
|
||||||
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -42,23 +43,27 @@ func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...Compo
|
|||||||
|
|
||||||
var renderer helm.Renderer
|
var renderer helm.Renderer
|
||||||
var err error
|
var err error
|
||||||
if newOpts.RepoURL != "" {
|
|
||||||
renderer, err = helm.NewRemoteRenderer(
|
// Istio can be installed by embed type or remote type
|
||||||
|
if strings.HasPrefix(newOpts.RepoURL, "embed://") {
|
||||||
|
chartDir := strings.TrimPrefix(newOpts.RepoURL, "embed://")
|
||||||
|
renderer, err = helm.NewLocalChartRenderer(
|
||||||
helm.WithName(newOpts.ChartName),
|
helm.WithName(newOpts.ChartName),
|
||||||
helm.WithNamespace(newOpts.Namespace),
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
helm.WithRepoURL(newOpts.RepoURL),
|
helm.WithRepoURL(newOpts.RepoURL),
|
||||||
helm.WithVersion(newOpts.Version),
|
helm.WithVersion(newOpts.Version),
|
||||||
|
helm.WithFS(manifests.BuiltinOrDir("")),
|
||||||
|
helm.WithDir(chartDir),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
renderer, err = helm.NewLocalRenderer(
|
renderer, err = helm.NewRemoteRenderer(
|
||||||
helm.WithName(newOpts.ChartName),
|
helm.WithName(newOpts.ChartName),
|
||||||
helm.WithNamespace(newOpts.Namespace),
|
helm.WithNamespace(newOpts.Namespace),
|
||||||
|
helm.WithRepoURL(newOpts.RepoURL),
|
||||||
helm.WithVersion(newOpts.Version),
|
helm.WithVersion(newOpts.Version),
|
||||||
helm.WithFS(os.DirFS(newOpts.ChartPath)),
|
|
||||||
helm.WithDir(string(Istio)),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
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)
|
generate := newManifestGenerateCmd(iArgs)
|
||||||
addManifestFlags(generate, iArgs)
|
addManifestFlags(generate, iArgs)
|
||||||
|
flags := generate.Flags()
|
||||||
|
options.AddKubeConfigFlags(flags)
|
||||||
manifestCmd.AddCommand(generate)
|
manifestCmd.AddCommand(generate)
|
||||||
|
|
||||||
return manifestCmd
|
return manifestCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func addManifestFlags(cmd *cobra.Command, args *ManifestArgs) {
|
func addManifestFlags(cmd *cobra.Command, args *ManifestArgs) {
|
||||||
|
cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
|
||||||
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
||||||
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
}
|
}
|
||||||
@@ -123,7 +126,7 @@ func genManifests(profile *helm.Profile, writer io.Writer) error {
|
|||||||
return fmt.Errorf("failed to build kubernetes client: %w", err)
|
return fmt.Errorf("failed to build kubernetes client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
op, err := installer.NewInstaller(profile, cliClient, writer, true)
|
op, err := installer.NewK8sInstaller(profile, cliClient, writer, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
// FS embeds the manifests
|
||||||
//
|
//
|
||||||
//go:embed profiles/*
|
//go:embed profiles/*
|
||||||
|
//go:embed gatewayapi/*
|
||||||
|
//go:embed istiobase/*
|
||||||
var FS embed.FS
|
var FS embed.FS
|
||||||
|
|
||||||
// BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in
|
// BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in
|
||||||
|
|||||||
@@ -1,38 +1,16 @@
|
|||||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
profile: all
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
profile: kind
|
|
||||||
global:
|
global:
|
||||||
install: local # install mode k8s/local/docker
|
install: k8s # install mode k8s/local-k8s/local-docker/local
|
||||||
ingressClass: higress
|
ingressClass: higress
|
||||||
watchNamespace:
|
|
||||||
disableAlpnH2: true
|
|
||||||
enableStatus: true
|
|
||||||
enableIstioAPI: true
|
enableIstioAPI: true
|
||||||
|
enableGatewayAPI: false
|
||||||
namespace: higress-system
|
namespace: higress-system
|
||||||
istioNamespace: istio-system
|
|
||||||
|
|
||||||
console:
|
console:
|
||||||
port: 8080
|
port: 8080
|
||||||
replicas: 1
|
replicas: 1
|
||||||
serviceType: ClusterIP
|
|
||||||
domain: console.higress.io
|
|
||||||
tlsSecretName:
|
|
||||||
webLoginPrompt:
|
|
||||||
adminPasswordValue: admin
|
adminPasswordValue: admin
|
||||||
adminPasswordLength: 8
|
|
||||||
o11yEnabled: false
|
o11yEnabled: false
|
||||||
pvcRwxSupported: true
|
|
||||||
|
|
||||||
gateway:
|
gateway:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
@@ -44,7 +22,7 @@ controller:
|
|||||||
replicas: 1
|
replicas: 1
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
url: nacos://192.168.0.1:8848 # file://opt/higress/conf, buildin://127.0.0.1:8848
|
url: nacos://127.0.0.1:8848 # file://opt/higress/conf
|
||||||
ns: higress-system
|
ns: higress-system
|
||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
@@ -59,7 +37,7 @@ charts:
|
|||||||
url: https://higress.io/helm-charts
|
url: https://higress.io/helm-charts
|
||||||
name: higress
|
name: higress
|
||||||
version: latest
|
version: latest
|
||||||
istio:
|
standalone:
|
||||||
url: https://istio-release.storage.googleapis.com/charts
|
url: https://higress.io/standalone/get-higress.sh
|
||||||
name: base
|
name: standalone
|
||||||
version: 1.18.2
|
version: latest
|
||||||
@@ -1,37 +1,15 @@
|
|||||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
profile: k8s
|
profile: k8s
|
||||||
global:
|
global:
|
||||||
install: k8s # install mode k8s/local-k8s/local-docker/local
|
install: k8s # install mode k8s/local-k8s/local-docker/local
|
||||||
ingressClass: higress
|
ingressClass: higress
|
||||||
watchNamespace:
|
|
||||||
disableAlpnH2: true
|
|
||||||
enableStatus: true
|
|
||||||
enableIstioAPI: false
|
enableIstioAPI: false
|
||||||
|
enableGatewayAPI: false
|
||||||
namespace: higress-system
|
namespace: higress-system
|
||||||
istioNamespace: istio-system
|
|
||||||
|
|
||||||
console:
|
console:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
serviceType: ClusterIP
|
|
||||||
domain: console.higress.io
|
|
||||||
tlsSecretName:
|
|
||||||
webLoginPrompt:
|
|
||||||
adminPasswordValue: admin
|
adminPasswordValue: admin
|
||||||
adminPasswordLength: 8
|
|
||||||
o11yEnabled: false
|
o11yEnabled: false
|
||||||
pvcRwxSupported: true
|
|
||||||
|
|
||||||
gateway:
|
gateway:
|
||||||
replicas: 2
|
replicas: 2
|
||||||
@@ -47,7 +25,7 @@ charts:
|
|||||||
url: https://higress.io/helm-charts
|
url: https://higress.io/helm-charts
|
||||||
name: higress
|
name: higress
|
||||||
version: latest
|
version: latest
|
||||||
istio:
|
standalone:
|
||||||
url: https://istio-release.storage.googleapis.com/charts
|
url: https://higress.io/standalone/get-higress.sh
|
||||||
name: base
|
name: standalone
|
||||||
version: 1.18.2
|
version: latest
|
||||||
|
|||||||
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
|
profile: local-k8s
|
||||||
global:
|
global:
|
||||||
install: local-k8s # install mode k8s/local-k8s/local-docker/local
|
install: local-k8s # install mode k8s/local-k8s/local-docker/local
|
||||||
ingressClass: higress
|
ingressClass: higress
|
||||||
watchNamespace:
|
|
||||||
disableAlpnH2: true
|
|
||||||
enableStatus: true
|
|
||||||
enableIstioAPI: true
|
enableIstioAPI: true
|
||||||
|
enableGatewayAPI: true
|
||||||
namespace: higress-system
|
namespace: higress-system
|
||||||
istioNamespace: istio-system
|
|
||||||
|
|
||||||
console:
|
console:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
serviceType: ClusterIP
|
|
||||||
domain: console.higress.io
|
|
||||||
tlsSecretName:
|
|
||||||
webLoginPrompt:
|
|
||||||
adminPasswordValue: admin
|
adminPasswordValue: admin
|
||||||
adminPasswordLength: 8
|
|
||||||
o11yEnabled: true
|
o11yEnabled: true
|
||||||
pvcRwxSupported: true
|
|
||||||
|
|
||||||
gateway:
|
gateway:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
@@ -47,7 +25,7 @@ charts:
|
|||||||
url: https://higress.io/helm-charts
|
url: https://higress.io/helm-charts
|
||||||
name: higress
|
name: higress
|
||||||
version: latest
|
version: latest
|
||||||
istio:
|
standalone:
|
||||||
url: https://istio-release.storage.googleapis.com/charts
|
url: https://higress.io/standalone/get-higress.sh
|
||||||
name: base
|
name: standalone
|
||||||
version: 1.18.2
|
version: latest
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
||||||
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
|
|
||||||
"github.com/alibaba/higress/pkg/cmd/options"
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -29,18 +28,10 @@ import (
|
|||||||
type uninstallArgs struct {
|
type uninstallArgs struct {
|
||||||
// purgeIstioCRD delete all of Istio resources.
|
// purgeIstioCRD delete all of Istio resources.
|
||||||
purgeIstioCRD bool
|
purgeIstioCRD bool
|
||||||
// istioNamespace is the target namespace of istio control plane.
|
|
||||||
istioNamespace string
|
|
||||||
// namespace is the namespace of higress installed .
|
|
||||||
namespace string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUninstallFlags(cmd *cobra.Command, args *uninstallArgs) {
|
func addUninstallFlags(cmd *cobra.Command, args *uninstallArgs) {
|
||||||
cmd.PersistentFlags().StringVar(&args.istioNamespace, "istio-namespace", "istio-system",
|
cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "", false,
|
||||||
"The namespace of Istio Control Plane.")
|
|
||||||
cmd.PersistentFlags().StringVarP(&args.namespace, "namespace", "n", "higress-system",
|
|
||||||
"The namespace of higress")
|
|
||||||
cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "p", false,
|
|
||||||
"Delete all of Istio resources")
|
"Delete all of Istio resources")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,15 +41,13 @@ func newUninstallCmd() *cobra.Command {
|
|||||||
uninstallCmd := &cobra.Command{
|
uninstallCmd := &cobra.Command{
|
||||||
Use: "uninstall",
|
Use: "uninstall",
|
||||||
Short: "Uninstall higress from a cluster",
|
Short: "Uninstall higress from a cluster",
|
||||||
Long: "The uninstall command uninstalls higress from a cluster",
|
Long: "The uninstall command uninstalls higress from a cluster or local environment",
|
||||||
Example: ` # Uninstall higress
|
Example: ` # Uninstall higress
|
||||||
hgctl uninstall
|
hgctl uninstal
|
||||||
|
|
||||||
# Uninstall higress by special namespace
|
# Uninstall higress and istio CRD from a cluster
|
||||||
hgctl uninstall --namespace=higress-system
|
hgctl uninstall --purge-istio-crd
|
||||||
|
`,
|
||||||
# Uninstall higress and istio CRD
|
|
||||||
hgctl uninstall --purge-istio-crd --istio-namespace=istio-system`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return uninstall(cmd.OutOrStdout(), uiArgs)
|
return uninstall(cmd.OutOrStdout(), uiArgs)
|
||||||
},
|
},
|
||||||
@@ -71,24 +60,36 @@ func newUninstallCmd() *cobra.Command {
|
|||||||
|
|
||||||
// uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests.
|
// uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests.
|
||||||
func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
|
func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
|
||||||
|
profileName, ok := installer.GetInstalledYamlPath()
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(writer, "\nHigress hasn't been installed yet!\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
setFlags := make([]string, 0)
|
setFlags := make([]string, 0)
|
||||||
profileName := helm.GetUninstallProfileName()
|
|
||||||
_, profile, err := helm.GenProfile(profileName, "", setFlags)
|
_, profile, err := helm.GenProfile(profileName, "", setFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
|
||||||
|
err = profile.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if !promptUninstall(writer) {
|
if !promptUninstall(writer) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
|
if profile.Global.Install == helm.InstallK8s || profile.Global.Install == helm.InstallLocalK8s {
|
||||||
profile.Global.Namespace = uiArgs.namespace
|
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
|
||||||
profile.Global.IstioNamespace = uiArgs.istioNamespace
|
}
|
||||||
err = UnInstallManifests(profile, writer)
|
|
||||||
|
err = uninstallManifests(profile, writer, uiArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,29 +109,16 @@ func promptUninstall(writer io.Writer) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnInstallManifests(profile *helm.Profile, writer io.Writer) error {
|
func uninstallManifests(profile *helm.Profile, writer io.Writer, uiArgs *uninstallArgs) error {
|
||||||
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
|
installer, err := installer.NewInstaller(profile, writer, false)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to build kubernetes client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
op, err := installer.NewInstaller(profile, cliClient, writer, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := op.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
manifestMap, err := op.RenderManifests()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(writer, "\n⌛️ Processing uninstallation... \n\n")
|
err = installer.UnInstall()
|
||||||
if err := op.DeleteManifests(manifestMap); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, "\n🎊 Uninstall All Resources Complete!\n")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
package hgctl
|
package hgctl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
|
||||||
|
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
|
||||||
"github.com/alibaba/higress/pkg/cmd/options"
|
"github.com/alibaba/higress/pkg/cmd/options"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -24,6 +30,7 @@ type upgradeArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addUpgradeFlags(cmd *cobra.Command, args *upgradeArgs) {
|
func addUpgradeFlags(cmd *cobra.Command, args *upgradeArgs) {
|
||||||
|
cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
|
||||||
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
|
||||||
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
|
||||||
}
|
}
|
||||||
@@ -39,7 +46,7 @@ func newUpgradeCmd() *cobra.Command {
|
|||||||
Long: "The upgrade command is an alias for the install command" +
|
Long: "The upgrade command is an alias for the install command" +
|
||||||
" that performs additional upgrade-related checks.",
|
" that performs additional upgrade-related checks.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) (e error) {
|
RunE: func(cmd *cobra.Command, args []string) (e error) {
|
||||||
return Install(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
|
return upgrade(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
addUpgradeFlags(upgradeCmd, upgradeArgs)
|
addUpgradeFlags(upgradeCmd, upgradeArgs)
|
||||||
@@ -47,3 +54,70 @@ func newUpgradeCmd() *cobra.Command {
|
|||||||
options.AddKubeConfigFlags(flags)
|
options.AddKubeConfigFlags(flags)
|
||||||
return upgradeCmd
|
return upgradeCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upgrade upgrade higress resources from the cluster.
|
||||||
|
func upgrade(writer io.Writer, iArgs *InstallArgs) error {
|
||||||
|
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
|
||||||
|
profileName, ok := installer.GetInstalledYamlPath()
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(writer, "\nHigress hasn't been installed yet!\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
valuesOverlay, err := helm.GetValuesOverylayFromFiles(iArgs.InFilenames)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, profile, err := helm.GenProfile(profileName, valuesOverlay, setFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
|
||||||
|
err = profile.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !promptUpgrade(writer) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = upgradeManifests(profile, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptUpgrade(writer io.Writer) bool {
|
||||||
|
answer := ""
|
||||||
|
for {
|
||||||
|
fmt.Fprintf(writer, "All Higress resources will be upgraed from the cluster. \nProceed? (y/N)")
|
||||||
|
fmt.Scanln(&answer)
|
||||||
|
if strings.TrimSpace(answer) == "y" {
|
||||||
|
fmt.Fprintf(writer, "\n")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(answer) == "N" {
|
||||||
|
fmt.Fprintf(writer, "Cancelled.\n")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgradeManifests(profile *helm.Profile, writer io.Writer) error {
|
||||||
|
installer, err := installer.NewInstaller(profile, writer, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = installer.Upgrade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
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
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -76,3 +78,18 @@ func ParseValue(valueStr string) any {
|
|||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteFileString write string content to file
|
||||||
|
func WriteFileString(fileName string, content string, perm os.FileMode) error {
|
||||||
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
writer := bufio.NewWriter(file)
|
||||||
|
if _, err := writer.WriteString(content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user