blob: 92bbd370e1c99c2ddf2bc4a19f86275cf20f93b8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 kube
import (
"path"
"strings"
"unicode/utf8"
)
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-kubernetes/app/dubboctl/identifier"
"github.com/apache/dubbo-kubernetes/app/dubboctl/internal/apis/dubbo.apache.org/v1alpha1"
"github.com/apache/dubbo-kubernetes/app/dubboctl/internal/filesystem"
"github.com/apache/dubbo-kubernetes/app/dubboctl/internal/manifest"
"github.com/apache/dubbo-kubernetes/app/dubboctl/internal/manifest/render"
"github.com/apache/dubbo-kubernetes/app/dubboctl/internal/util"
)
type ComponentName string
const (
Admin ComponentName = "admin"
Grafana ComponentName = "grafana"
Nacos ComponentName = "nacos"
Zookeeper ComponentName = "zookeeper"
Prometheus ComponentName = "prometheus"
Skywalking ComponentName = "skywalking"
Zipkin ComponentName = "zipkin"
)
var ComponentMap = map[string]ComponentName{
"admin": Admin,
"grafana": Grafana,
"nacos": Nacos,
"zookeeper": Zookeeper,
"prometheus": Prometheus,
"skywalking": Skywalking,
"zipkin": Zipkin,
}
// Component is used to represent dubbo control plane module, eg: zookeeper
type Component interface {
Run() error
RenderManifest() (string, error)
}
type ComponentOptions struct {
Namespace string
// local
ChartPath string
// remote
RepoURL string
Version string
}
type ComponentOption func(*ComponentOptions)
func WithNamespace(namespace string) ComponentOption {
return func(opts *ComponentOptions) {
opts.Namespace = namespace
}
}
func WithChartPath(path string) ComponentOption {
return func(opts *ComponentOptions) {
opts.ChartPath = path
}
}
func WithRepoURL(url string) ComponentOption {
return func(opts *ComponentOptions) {
opts.RepoURL = url
}
}
func WithVersion(version string) ComponentOption {
return func(opts *ComponentOptions) {
opts.Version = version
}
}
type AdminComponent struct {
spec *v1alpha1.AdminSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (ac *AdminComponent) Run() error {
if err := ac.renderer.Init(); err != nil {
return err
}
ac.started = true
return nil
}
func (ac *AdminComponent) RenderManifest() (string, error) {
if !ac.started {
return "", nil
}
manifest, err := renderManifest(ac.spec, ac.renderer, false, Admin, ac.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewAdminComponent(spec *v1alpha1.AdminSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// verify newOpts
renderer, err := render.NewLocalRenderer(
render.WithName(string(Admin)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("admin"))
if err != nil {
return nil, err
}
// todo: verify spec
admin := &AdminComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return admin, nil
}
type GrafanaComponent struct {
spec *v1alpha1.GrafanaSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (gc *GrafanaComponent) Run() error {
if err := gc.renderer.Init(); err != nil {
return err
}
gc.started = true
return nil
}
func (gc *GrafanaComponent) RenderManifest() (string, error) {
if !gc.started {
return "", nil
}
manifest, err := renderManifest(gc.spec, gc.renderer, true, Grafana, gc.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewGrafanaComponent(spec *v1alpha1.GrafanaSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// todo: verify newOpts
var renderer render.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = render.NewRemoteRenderer(
render.WithName(string(Grafana)),
render.WithNamespace(newOpts.Namespace),
render.WithRepoURL(newOpts.RepoURL),
render.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = render.NewLocalRenderer(
render.WithName(string(Grafana)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("grafana"),
)
if err != nil {
return nil, err
}
}
// todo: verify spec
grafana := &GrafanaComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return grafana, nil
}
type NacosComponent struct {
spec *v1alpha1.NacosSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (nc *NacosComponent) Run() error {
if err := nc.renderer.Init(); err != nil {
return err
}
nc.started = true
return nil
}
func (nc *NacosComponent) RenderManifest() (string, error) {
if !nc.started {
return "", nil
}
manifest, err := renderManifest(nc.spec, nc.renderer, false, Nacos, nc.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewNacosComponent(spec *v1alpha1.NacosSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// verify newOpts
renderer, err := render.NewLocalRenderer(
render.WithName(string(Nacos)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("nacos"))
if err != nil {
return nil, err
}
// todo: verify spec
nacos := &NacosComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return nacos, nil
}
type ZookeeperComponent struct {
spec *v1alpha1.ZookeeperSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (zc *ZookeeperComponent) Run() error {
if err := zc.renderer.Init(); err != nil {
return err
}
zc.started = true
return nil
}
func (zc *ZookeeperComponent) RenderManifest() (string, error) {
if !zc.started {
return "", nil
}
manifest, err := renderManifest(zc.spec, zc.renderer, true, Zookeeper, zc.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewZookeeperComponent(spec *v1alpha1.ZookeeperSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// verify newOpts
var renderer render.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = render.NewRemoteRenderer(
render.WithName(string(Zookeeper)),
render.WithNamespace(newOpts.Namespace),
render.WithRepoURL(newOpts.RepoURL),
render.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = render.NewLocalRenderer(
render.WithName(string(Zookeeper)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("zookeeper"),
)
if err != nil {
return nil, err
}
}
// todo: verify spec
zookeeper := &ZookeeperComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return zookeeper, nil
}
type PrometheusComponent struct {
spec *v1alpha1.PrometheusSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (pc *PrometheusComponent) Run() error {
if err := pc.renderer.Init(); err != nil {
return err
}
pc.started = true
return nil
}
func (pc *PrometheusComponent) RenderManifest() (string, error) {
if !pc.started {
return "", nil
}
manifest, err := renderManifest(pc.spec, pc.renderer, true, Prometheus, pc.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewPrometheusComponent(spec *v1alpha1.PrometheusSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// todo: verify newOpts
var renderer render.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = render.NewRemoteRenderer(
render.WithName(string(Prometheus)),
render.WithNamespace(newOpts.Namespace),
render.WithRepoURL(newOpts.RepoURL),
render.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = render.NewLocalRenderer(
render.WithName(string(Prometheus)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("prometheus"),
)
if err != nil {
return nil, err
}
}
// todo: verify spec
prometheus := &PrometheusComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return prometheus, nil
}
type SkywalkingComponent struct {
spec *v1alpha1.SkywalkingSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (sc *SkywalkingComponent) Run() error {
if err := sc.renderer.Init(); err != nil {
return err
}
sc.started = true
return nil
}
func (sc *SkywalkingComponent) RenderManifest() (string, error) {
if !sc.started {
return "", nil
}
manifest, err := renderManifest(sc.spec, sc.renderer, true, Skywalking, sc.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewSkywalkingComponent(spec *v1alpha1.SkywalkingSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// todo: verify newOpts
var renderer render.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = render.NewRemoteRenderer(
render.WithName(string(Skywalking)),
render.WithNamespace(newOpts.Namespace),
render.WithRepoURL(newOpts.RepoURL),
render.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = render.NewLocalRenderer(
render.WithName(string(Skywalking)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("skywalking"),
)
if err != nil {
return nil, err
}
}
// todo: verify spec
skywalking := &SkywalkingComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return skywalking, nil
}
type ZipkinComponent struct {
spec *v1alpha1.ZipkinSpec
renderer render.Renderer
started bool
opts *ComponentOptions
}
func (zc *ZipkinComponent) Run() error {
if err := zc.renderer.Init(); err != nil {
return err
}
zc.started = true
return nil
}
func (zc *ZipkinComponent) RenderManifest() (string, error) {
if !zc.started {
return "", nil
}
manifest, err := renderManifest(zc.spec, zc.renderer, true, Zipkin, zc.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}
func NewZipkinComponent(spec *v1alpha1.ZipkinSpec, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
// todo: verify newOpts
var renderer render.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = render.NewRemoteRenderer(
render.WithName(string(Zipkin)),
render.WithNamespace(newOpts.Namespace),
render.WithRepoURL(newOpts.RepoURL),
render.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = render.NewLocalRenderer(
render.WithName(string(Zipkin)),
render.WithNamespace(newOpts.Namespace),
render.WithFS(filesystem.NewSubFS(newOpts.ChartPath, identifier.UnionFS)),
render.WithDir("zipkin"),
)
if err != nil {
return nil, err
}
}
// todo: verify spec
zipkin := &ZipkinComponent{
spec: spec,
renderer: renderer,
opts: newOpts,
}
return zipkin, nil
}
func renderManifest(spec any, renderer render.Renderer, addOn bool, name ComponentName, namespace string) (string, error) {
var valsBytes []byte
var valsYaml string
var err error
if addOn {
// see /deploy/addons
// values-*.yaml is the base yaml for addon bootstrap
valsYaml, err = manifest.ReadAndOverlayYamls([]string{
path.Join(identifier.Addons, "values-"+string(name)+".yaml"),
})
if err != nil {
return "", err
}
}
// do not use spec != nil cause spec's type is not nil
if !util.IsValueNil(spec) {
valsBytes, err = yaml.Marshal(spec)
if err != nil {
return "", err
}
valsYaml, err = util.OverlayYAML(valsYaml, string(valsBytes))
if err != nil {
return "", err
}
}
final, err := renderer.RenderManifest(valsYaml)
if err != nil {
return "", err
}
// grafana needs to add dashboard json file as configmap
if name == Grafana {
final, err = addDashboards(final, namespace)
if err != nil {
return "", err
}
}
// manifest rendered by some charts may lack namespace, we set it there
final, err = setNamespace(final, namespace)
if err != nil {
return "", err
}
return final, nil
}
// Hack function of grafana bootstrap. It needs external dashboard json file.
// We would consider design a more robust way to add dashboards files or merge these files to grafana chart.
// Assume that base ends with yaml separator.
func addDashboards(base string, namespace string) (string, error) {
configMap := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "admin-extra-dashboards",
Namespace: namespace,
},
}
configMap.Data = make(map[string]string)
dashboardName := "external-dashboard.json"
dashboardPath := path.Join(identifier.AddonDashboards, dashboardName)
content, err := identifier.UnionFS.ReadFile(dashboardPath)
if err != nil {
return "", err
}
// values with non-UTF-8 byte sequences must use the BinaryData field.
if utf8.Valid(content) {
configMap.Data[dashboardName] = string(content)
} else {
configMap.BinaryData[dashboardName] = content
}
yamlBytes, err := yaml.Marshal(configMap)
if err != nil {
return "", err
}
yamlStr := strings.TrimSpace(string(yamlBytes))
final := base + yamlStr + render.YAMLSeparator
return final, nil
}
// setNamespace split base and set namespace.
func setNamespace(base string, namespace string) (string, error) {
var newSegs []string
segs, err := util.SplitYAML(base)
if err != nil {
return "", err
}
for _, seg := range segs {
segMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(seg), &segMap); err != nil {
return "", err
}
pathCtx, _, err := manifest.GetPathContext(segMap, util.PathFromString("metadata.namespace"), true)
if err != nil {
return "", err
}
if err := manifest.WritePathContext(pathCtx, manifest.ParseValue(namespace), false); err != nil {
return "", err
}
newSeg, err := yaml.Marshal(segMap)
if err != nil {
return "", err
}
newSegs = append(newSegs, string(newSeg))
}
final := util.JoinYAML(newSegs)
return final, nil
}