Files
higress/pkg/cmd/hgctl/installer/component.go
2023-09-21 13:44:09 +08:00

297 lines
6.9 KiB
Go

// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package installer
import (
"fmt"
"io"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"sigs.k8s.io/yaml"
)
type ComponentName string
const (
Higress ComponentName = "higress"
Istio ComponentName = "istio"
)
var ComponentMap = map[string]ComponentName{
"higress": Higress,
"istio": Istio,
}
type Component interface {
// ComponentName returns the name of the component.
ComponentName() ComponentName
// Namespace returns the namespace for the component.
Namespace() string
// Enabled reports whether the component is enabled.
Enabled() bool
// Run starts the component. Must be called before the component is used.
Run() error
RenderManifest() (string, error)
}
type ComponentOptions struct {
Name string
Namespace string
// local
ChartPath string
// remote
RepoURL string
ChartName string
Version string
}
type ComponentOption func(*ComponentOptions)
func WithComponentNamespace(namespace string) ComponentOption {
return func(opts *ComponentOptions) {
opts.Namespace = namespace
}
}
func WithComponentChartPath(path string) ComponentOption {
return func(opts *ComponentOptions) {
opts.ChartPath = path
}
}
func WithComponentChartName(chartName string) ComponentOption {
return func(opts *ComponentOptions) {
opts.ChartName = chartName
}
}
func WithComponentRepoURL(url string) ComponentOption {
return func(opts *ComponentOptions) {
opts.RepoURL = url
}
}
func WithComponentVersion(version string) ComponentOption {
return func(opts *ComponentOptions) {
opts.Version = version
}
}
type HigressComponent struct {
profile *helm.Profile
started bool
opts *ComponentOptions
renderer helm.Renderer
writer io.Writer
}
func (h *HigressComponent) ComponentName() ComponentName {
return Higress
}
func (h *HigressComponent) Namespace() string {
return h.opts.Namespace
}
func (h *HigressComponent) Enabled() bool {
return true
}
func (h *HigressComponent) Run() error {
// Parse latest version
if h.opts.Version == helm.RepoLatestVersion {
latestVersion, err := helm.ParseLatestVersion(h.opts.RepoURL, h.opts.Version)
if err != nil {
return err
}
fmt.Fprintf(h.writer, "⚡️ Fetching Higress Helm Chart latest version \"%s\" \n", latestVersion)
// Reset Helm Chart version
h.opts.Version = latestVersion
h.renderer.SetVersion(latestVersion)
}
fmt.Fprintf(h.writer, "🏄 Downloading Higress Helm Chart version: %s, url: %s\n", h.opts.Version, h.opts.RepoURL)
if err := h.renderer.Init(); err != nil {
return err
}
h.started = true
return nil
}
func (h *HigressComponent) RenderManifest() (string, error) {
if !h.started {
return "", nil
}
fmt.Fprintf(h.writer, "📦 Rendering Higress Helm Chart\n")
valsYaml, err := h.profile.ValuesYaml()
if err != nil {
return "", err
}
manifest, err2 := renderComponentManifest(valsYaml, h.renderer, true, h.ComponentName(), h.opts.Namespace)
if err2 != nil {
return "", err
}
return manifest, nil
}
func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
var renderer helm.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = helm.NewRemoteRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL),
helm.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = helm.NewLocalRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithVersion(newOpts.Version),
helm.WithFS(os.DirFS(newOpts.ChartPath)),
helm.WithDir(string(Higress)),
)
if err != nil {
return nil, err
}
}
higressComponent := &HigressComponent{
profile: profile,
renderer: renderer,
opts: newOpts,
writer: writer,
}
return higressComponent, nil
}
type IstioCRDComponent struct {
profile *helm.Profile
started bool
opts *ComponentOptions
renderer helm.Renderer
writer io.Writer
}
func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
var renderer helm.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = helm.NewRemoteRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL),
helm.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = helm.NewLocalRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithVersion(newOpts.Version),
helm.WithFS(os.DirFS(newOpts.ChartPath)),
helm.WithDir(string(Istio)),
)
if err != nil {
return nil, err
}
}
istioComponent := &IstioCRDComponent{
profile: profile,
renderer: renderer,
opts: newOpts,
writer: writer,
}
return istioComponent, nil
}
func (i *IstioCRDComponent) ComponentName() ComponentName {
return Istio
}
func (i *IstioCRDComponent) Namespace() string {
return i.opts.Namespace
}
func (i *IstioCRDComponent) Enabled() bool {
return true
}
func (i *IstioCRDComponent) Run() error {
fmt.Fprintf(i.writer, "🏄 Downloading Istio Helm Chart 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 *IstioCRDComponent) RenderManifest() (string, error) {
if !i.started {
return "", nil
}
fmt.Fprintf(i.writer, "📦 Rendering Istio Helm Chart\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
}
func renderComponentManifest(spec any, renderer helm.Renderer, addOn bool, name ComponentName, namespace string) (string, error) {
var valsBytes []byte
var valsYaml string
var err error
if yamlString, ok := spec.(string); ok {
valsYaml = yamlString
} else {
if !util.IsValueNil(spec) {
valsBytes, err = yaml.Marshal(spec)
if err != nil {
return "", err
}
valsYaml = string(valsBytes)
}
}
final, err := renderer.RenderManifest(valsYaml)
if err != nil {
return "", err
}
return final, nil
}