blob: 4238731fbb33cb235b95e5d200dbfcd70aa9a8d9 [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 (
"context"
"fmt"
"time"
)
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// CtlClient wraps controller-runtime clientgen and is used by dubboctl
type CtlClient struct {
opts *CtlClientOptions
client.Client
}
type CtlClientOptions struct {
// for normal use
// path to kubeconfig
KubeConfigPath string
// specify cluster in kubeconfig to use
Context string
// for test
Cli client.Client
}
type CtlClientOption func(*CtlClientOptions)
func WithKubeConfigPath(path string) CtlClientOption {
return func(opts *CtlClientOptions) {
opts.KubeConfigPath = path
}
}
func WithContext(ctx string) CtlClientOption {
return func(opts *CtlClientOptions) {
opts.Context = ctx
}
}
func WithCli(cli client.Client) CtlClientOption {
return func(opts *CtlClientOptions) {
opts.Cli = cli
}
}
// ApplyManifest applies manifest to certain namespace
// If there is not this namespace, create it first
func (cli *CtlClient) ApplyManifest(manifest string, ns string, name ComponentName) error {
if err := cli.CreateNamespace(ns); err != nil {
return err
}
objs, err := ParseObjectsFromManifest(manifest, false)
if err != nil {
return err
}
for _, obj := range objs {
o := obj.Unstructured()
if obj.Namespace == "" {
obj.SetNamespace(ns)
}
if err := cli.ApplyObject(o); err != nil {
return err
}
if name == Zookeeper && o.GetKind() == "Service" {
labels := o.GetLabels()
labels["dubbo.apache.org/zookeeper"] = "true"
o.SetLabels(labels)
err := cli.Update(context.Background(), o)
if err != nil {
return err
}
} else if name == Nacos && o.GetKind() == "Service" {
labels := o.GetLabels()
labels["dubbo.apache.org/nacos"] = "true"
o.SetLabels(labels)
err := cli.Update(context.Background(), o)
if err != nil {
return err
}
} else if name == Prometheus && o.GetKind() == "Service" {
labels := o.GetLabels()
labels["dubbo.apache.org/prometheus"] = "true"
o.SetLabels(labels)
err := cli.Update(context.Background(), o)
if err != nil {
return err
}
}
}
return nil
}
// ApplyObject creates or updates unstructured object
func (cli *CtlClient) ApplyObject(obj *unstructured.Unstructured) error {
if obj.GetKind() == "List" {
objList, err := obj.ToList()
if err != nil {
return err
}
for _, item := range objList.Items {
if err := cli.ApplyObject(&item); err != nil {
return err
}
}
return nil
}
key := client.ObjectKeyFromObject(obj)
receiver := &unstructured.Unstructured{}
receiver.SetGroupVersionKind(obj.GroupVersionKind())
if err := retry.RetryOnConflict(wait.Backoff{
Duration: time.Millisecond * 10,
Factor: 2,
Steps: 3,
}, func() error {
if err := cli.Get(context.Background(), key, receiver); err != nil {
if errors.IsNotFound(err) {
if err := cli.Create(context.Background(), obj); err != nil {
return err
}
}
// log
return nil
}
if err := OverlayObject(receiver, obj); err != nil {
return err
}
// TODO We really don’t know the reason for this, and we still need to verify it.
if receiver.GetName() == "prometheus-alertmanager-test-connection" {
return nil
}
if err := cli.Update(context.Background(), receiver); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func (cli *CtlClient) CreateNamespace(ns string) error {
key := client.ObjectKey{
Namespace: metav1.NamespaceSystem,
Name: ns,
}
namespace := &corev1.Namespace{}
err := cli.Get(context.Background(), key, namespace)
if err != nil {
if errors.IsNotFound(err) {
nsObj := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Namespace: metav1.NamespaceSystem,
Name: ns,
Labels: map[string]string{
"dubbo-deploy": "enabled",
},
},
}
if err := cli.Create(context.Background(), nsObj); err != nil {
return err
}
return nil
} else {
return fmt.Errorf("failed to check if namespace %v exists: %v", ns, err)
}
}
labels := namespace.Labels
labels["dubbo-deploy"] = "enabled"
err = cli.Update(context.Background(), namespace)
if err != nil {
return err
}
return nil
}
func (cli *CtlClient) RemoveManifest(manifest string, ns string) error {
objs, err := ParseObjectsFromManifest(manifest, false)
if err != nil {
return err
}
for _, obj := range objs {
if obj.Namespace == "" {
obj.SetNamespace(ns)
}
if err := cli.RemoveObject(obj.Unstructured()); err != nil {
return err
}
}
if err := cli.deleteNamespace(ns); err != nil {
return err
}
return nil
}
func (cli *CtlClient) RemoveObject(obj *unstructured.Unstructured) error {
if obj.GetKind() == "List" {
objList, err := obj.ToList()
if err != nil {
return err
}
for _, item := range objList.Items {
if err := cli.RemoveObject(&item); err != nil {
return err
}
}
return nil
}
key := client.ObjectKeyFromObject(obj)
receiver := &unstructured.Unstructured{}
receiver.SetGroupVersionKind(obj.GroupVersionKind())
if err := retry.RetryOnConflict(wait.Backoff{
Duration: time.Millisecond * 10,
Factor: 2,
Steps: 3,
}, func() error {
if err := cli.Get(context.Background(), key, receiver); err != nil {
if !errors.IsNotFound(err) {
// log
return err
}
return nil
}
if err := cli.Delete(context.Background(), receiver, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func (cli *CtlClient) deleteNamespace(ns string) error {
key := client.ObjectKey{
Namespace: metav1.NamespaceSystem,
Name: ns,
}
nsObj := &corev1.Namespace{}
if err := cli.Get(context.Background(), key, nsObj); err != nil {
if !errors.IsNotFound(err) {
// log
return fmt.Errorf("failed to check if namespace %v exists: %v", ns, err)
}
return nil
} else {
if err := cli.Delete(context.Background(), nsObj); err != nil {
return fmt.Errorf("failed to delete namespace: %s, err: %s", ns, err)
}
return nil
}
}
func NewCtlClient(opts ...CtlClientOption) (*CtlClient, error) {
var ctlCli *CtlClient
newOptions := &CtlClientOptions{}
for _, opt := range opts {
opt(newOptions)
}
// for test
if newOptions.Cli != nil {
ctlCli = &CtlClient{
Client: newOptions.Cli,
opts: newOptions,
}
return ctlCli, nil
}
cfg, err := BuildConfig(newOptions.KubeConfigPath, newOptions.Context)
if err != nil {
return nil, err
}
cli, err := client.New(cfg, client.Options{})
if err != nil {
return nil, err
}
ctlCli = &CtlClient{
Client: cli,
opts: newOptions,
}
return ctlCli, nil
}