Skip to content

Commit

Permalink
chore: add helm_tools for upgrading kubeblocks (#7382)
Browse files Browse the repository at this point in the history
  • Loading branch information
sophon-zt committed May 20, 2024
1 parent a1de2ec commit bfa1e9b
Show file tree
Hide file tree
Showing 10 changed files with 652 additions and 1 deletion.
76 changes: 76 additions & 0 deletions cmd/helmhook/hook/addon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright (C) 2022-2024 ApeCloud Co., 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 hook

import (
"context"
"encoding/json"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
errors "sigs.k8s.io/controller-runtime/pkg/client"

extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1"
"github.com/apecloud/kubeblocks/pkg/client/clientset/versioned"
)

type Addon struct{}

const (
helmResourcePolicyKey = "helm.sh/resource-policy"
helmResourcePolicyKeep = "keep"
)

func (p *Addon) Handle(ctx *UpgradeContext) (err error) {
addons, err := ctx.Client.ExtensionsV1alpha1().Addons().List(ctx, metav1.ListOptions{
LabelSelector: toLabelSelector(addonSelectorLabels()),
})

if err != nil {
return errors.IgnoreNotFound(err)
}

for _, addon := range addons.Items {
if addon.GetDeletionTimestamp() != nil {
continue
}
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
return patchAddon(ctx, ctx.Client, addon)
})
if err != nil {
return err
}
}
return nil
}

func patchAddon(ctx context.Context, client *versioned.Clientset, addon extensionsv1alpha1.Addon) error {
annotations := addon.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if addon.GetAnnotations()[helmResourcePolicyKey] == helmResourcePolicyKeep {
return nil
}
annotations[helmResourcePolicyKey] = helmResourcePolicyKeep
patchBytes, _ := json.Marshal(map[string]interface{}{"metadata": map[string]interface{}{"annotations": annotations}})

_, err := client.ExtensionsV1alpha1().Addons().Patch(ctx, addon.Name, types.MergePatchType,
patchBytes, metav1.PatchOptions{})
return err
}
135 changes: 135 additions & 0 deletions cmd/helmhook/hook/crd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
Copyright (C) 2022-2024 ApeCloud Co., 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 hook

import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/controller-runtime/pkg/client"

"k8s.io/apimachinery/pkg/util/sets"
)

type UpdateCRD struct {
}

func (p *UpdateCRD) Handle(ctx *UpgradeContext) (err error) {
crdList, err := parseCRDs(ctx.CRDPath)
if err != nil {
return err
}

for _, crd := range crdList {
_, err = ctx.CRDClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, crd.GetName(), metav1.GetOptions{})
if err == nil {
_, err = ctx.CRDClient.ApiextensionsV1().CustomResourceDefinitions().Create(ctx, &crd, metav1.CreateOptions{})
if err != nil {
return err
}
continue
}
if client.IgnoreNotFound(err) != nil {
return err
}
_, err = ctx.CRDClient.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, &crd, metav1.UpdateOptions{})
if err != nil {
return err
}
}
return nil
}

func parseCRDs(path string) ([]apiextensionsv1.CustomResourceDefinition, error) {
var (
err error
info os.FileInfo
files []string
filePath = path
)

// Return the error if ErrorIfPathMissing exists
if info, err = os.Stat(path); err != nil {
return nil, err
}
if !info.IsDir() {
return nil, fmt.Errorf("require path[%] is directory", path)
}

entries, err := os.ReadDir(path)
if err != nil {
return nil, err
}
for _, e := range entries {
files = append(files, e.Name())
}

fmt.Printf("reading CRDs from path: %s", path)
crdList, err := readCRDs(filePath, files)
if err != nil {
return nil, err
}
return crdList, nil
}

func readCRDs(basePath string, files []string) ([]apiextensionsv1.CustomResourceDefinition, error) {
var crds []apiextensionsv1.CustomResourceDefinition

crdExts := sets.NewString(".yaml", ".yml")
for _, file := range files {
if !crdExts.Has(filepath.Ext(file)) {
continue
}
docs, err := readDocuments(filepath.Join(basePath, file))
if err != nil {
return nil, err
}
crds = append(crds, docs...)
fmt.Sprintf("read CRDs from file: %s", file)
}
return crds, nil
}

func readDocuments(fp string) ([]apiextensionsv1.CustomResourceDefinition, error) {
b, err := os.ReadFile(fp)
if err != nil {
return nil, err
}

reader := k8syaml.NewYAMLToJSONDecoder(bufio.NewReader(bytes.NewReader(b)))

var objs []apiextensionsv1.CustomResourceDefinition
for {
var obj apiextensionsv1.CustomResourceDefinition
if err = reader.Decode(&obj); err != nil {
if err == io.EOF {
break
}
return nil, err
}
objs = append(objs, obj)
}

return objs, nil
}
50 changes: 50 additions & 0 deletions cmd/helmhook/hook/prepare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright (C) 2022-2024 ApeCloud Co., 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 hook

import (
"context"
"fmt"

"k8s.io/client-go/kubernetes"
)

type Prepare struct{}

const kubeblocksVersionLabelName = "app.kubernetes.io/version"

func (p *Prepare) Handle(ctx *UpgradeContext) (err error) {
ctx.From, err = getVersionInfo(ctx, ctx.K8sClient, ctx.Namespace)
return
}

func getVersionInfo(ctx context.Context, client *kubernetes.Clientset, namespace string) (*Version, error) {
deploy, err := GetKubeBlocksDeploy(ctx, client, namespace, kubeblocksAppComponent)
if err != nil {
return nil, err
}

labels := deploy.GetLabels()
if len(labels) == 0 {
return nil, fmt.Errorf("KubeBlocks deployment has no labels")
}

if v, ok := labels[kubeblocksVersionLabelName]; !ok {
return NewVersion(v), nil
}
return nil, fmt.Errorf("KubeBlocks deployment has no version label")
}
39 changes: 39 additions & 0 deletions cmd/helmhook/hook/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright (C) 2022-2024 ApeCloud Co., 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 hook

import (
"k8s.io/client-go/util/retry"
)

const (
// kubeblocksAppComponent the value of app.kubernetes.io/component label for KubeBlocks deployment
kubeblocksAppComponent = "apps"
// dataprotectionAppComponent the value of app.kubernetes.io/component label for DataProtection deployment
dataprotectionAppComponent = "dataprotection"
)

type StopOperator struct{}

func (p *StopOperator) Handle(ctx *UpgradeContext) error {
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
if err := stopKubeBlocksDeploy(ctx, ctx.K8sClient, ctx.Namespace, kubeblocksAppComponent, GetKubeBlocksDeploy); err != nil {
return err
}
return stopKubeBlocksDeploy(ctx, ctx.K8sClient, ctx.Namespace, dataprotectionAppComponent, GetKubeBlocksDeploy)
})
}
Loading

0 comments on commit bfa1e9b

Please sign in to comment.