karmada/operator/pkg/tasks/init/karmadaresource.go

217 lines
6.2 KiB
Go

package tasks
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"path"
"regexp"
"strings"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"github.com/karmada-io/karmada/operator/pkg/constants"
"github.com/karmada-io/karmada/operator/pkg/karmadaresource/apiservice"
"github.com/karmada-io/karmada/operator/pkg/karmadaresource/webhookconfiguration"
"github.com/karmada-io/karmada/operator/pkg/util"
"github.com/karmada-io/karmada/operator/pkg/util/apiclient"
"github.com/karmada-io/karmada/operator/pkg/workflow"
)
// NewKarmadaResourcesTask init KarmadaResources task
func NewKarmadaResourcesTask() workflow.Task {
return workflow.Task{
Name: "KarmadaResources",
Run: runKarmadaResources,
RunSubTasks: true,
Tasks: []workflow.Task{
{
Name: "systemNamespace",
Run: runSystemNamespace,
},
{
Name: "crds",
Run: runCrds,
},
{
Name: "WebhookConfiguration",
Run: runWebhookConfiguration,
},
{
Name: "APIService",
Run: runAPIService,
},
},
}
}
func runKarmadaResources(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("karmadaResources task invoked with an invalid data struct")
}
klog.V(4).InfoS("[karmadaResources] Running karmadaResources task", "karmada", klog.KObj(data))
return nil
}
func runSystemNamespace(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("systemName task invoked with an invalid data struct")
}
err := apiclient.CreateNamespace(data.KarmadaClient(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: constants.KarmadaSystemNamespace,
},
})
if err != nil {
return fmt.Errorf("failed to create namespace %s, err: %w", constants.KarmadaSystemNamespace, err)
}
klog.V(2).InfoS("[systemName] Successfully created karmada system namespace", "namespace", constants.KarmadaSystemNamespace, "karmada", klog.KObj(data))
return nil
}
func runCrds(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("crds task invoked with an invalid data struct")
}
var (
crdsDir = path.Join(data.DataDir(), data.KarmadaVersion())
crdsPath = path.Join(crdsDir, "crds/bases")
crdsPatchPath = path.Join(crdsDir, "crds/patches")
)
crdsClient, err := apiclient.NewCRDsClient(data.ControlplaneConfig())
if err != nil {
return err
}
if err := createCrds(crdsClient, crdsPath); err != nil {
return fmt.Errorf("failed to create karmada crds, err: %w", err)
}
cert := data.GetCert(constants.CaCertAndKeyName)
if len(cert.CertData()) == 0 {
return errors.New("unexpected empty ca cert data")
}
caBase64 := base64.StdEncoding.EncodeToString(cert.CertData())
if err := patchCrds(crdsClient, crdsPatchPath, caBase64); err != nil {
return fmt.Errorf("failed to patch karmada crds, err: %w", err)
}
klog.V(2).InfoS("[systemName] Successfully applied karmada crds resource", "karmada", klog.KObj(data))
return nil
}
func createCrds(crdsClient *crdsclient.Clientset, crdsPath string) error {
for _, file := range util.ListFileWithSuffix(crdsPath, ".yaml") {
crdBytes, err := util.ReadYamlFile(file.AbsPath)
if err != nil {
return err
}
obj := apiextensionsv1.CustomResourceDefinition{}
if err := json.Unmarshal(crdBytes, &obj); err != nil {
klog.ErrorS(err, "error when converting json byte to apiExtensionsV1 CustomResourceDefinition struct")
return err
}
if err := apiclient.CreateCustomResourceDefinitionIfNeed(crdsClient, &obj); err != nil {
return err
}
}
return nil
}
func patchCrds(crdsClient *crdsclient.Clientset, patchPath string, caBundle string) error {
for _, file := range util.ListFileWithSuffix(patchPath, ".yaml") {
reg, err := regexp.Compile("{{caBundle}}")
if err != nil {
return err
}
crdBytes, err := util.ReplaceYamlForReg(file.AbsPath, caBundle, reg)
if err != nil {
return err
}
crdResource := splitToCrdNameFormFile(file.Name(), "_", ".")
name := crdResource + ".work.karmada.io"
if err := apiclient.PatchCustomResourceDefinition(crdsClient, name, crdBytes); err != nil {
return err
}
}
return nil
}
func runWebhookConfiguration(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("[webhookConfiguration] task invoked with an invalid data struct")
}
cert := data.GetCert(constants.CaCertAndKeyName)
if len(cert.CertData()) == 0 {
return errors.New("unexpected empty ca cert data for webhookConfiguration")
}
caBase64 := base64.StdEncoding.EncodeToString(cert.CertData())
return webhookconfiguration.EnsureWebhookConfiguration(
data.KarmadaClient(),
data.GetNamespace(),
data.GetName(),
caBase64)
}
func runAPIService(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("webhookConfiguration task invoked with an invalid data struct")
}
config := data.ControlplaneConfig()
client, err := apiclient.NewAPIRegistrationClient(config)
if err != nil {
return err
}
cert := data.GetCert(constants.CaCertAndKeyName)
if len(cert.CertData()) == 0 {
return errors.New("unexpected empty ca cert data for aggregatedAPIService")
}
caBase64 := base64.StdEncoding.EncodeToString(cert.CertData())
err = apiservice.EnsureAggregatedAPIService(client, data.KarmadaClient(), data.GetName(), constants.KarmadaSystemNamespace, data.GetName(), data.GetNamespace(), caBase64)
if err != nil {
return fmt.Errorf("failed to apply aggregated APIService resource to karmada controlplane, err: %w", err)
}
waiter := apiclient.NewKarmadaWaiter(config, nil, componentBeReadyTimeout)
if err := waiter.WaitForAPIService(constants.APIServiceName); err != nil {
return fmt.Errorf("the APIService is unhealthy, err: %w", err)
}
klog.V(2).InfoS("[APIService] Aggregated APIService status is ready ", "karmada", klog.KObj(data))
return nil
}
func splitToCrdNameFormFile(file string, start, end string) string {
index := strings.LastIndex(file, start)
crdName := file[index+1:]
index = strings.Index(crdName, end)
if index > 0 {
crdName = crdName[:index]
}
return crdName
}