karmada/operator/pkg/init.go

234 lines
6.8 KiB
Go

package karmada
import (
"errors"
"fmt"
"net/url"
"sync"
utilversion "k8s.io/apimachinery/pkg/util/version"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
"github.com/karmada-io/karmada/operator/pkg/certs"
"github.com/karmada-io/karmada/operator/pkg/constants"
operatorscheme "github.com/karmada-io/karmada/operator/pkg/scheme"
tasks "github.com/karmada-io/karmada/operator/pkg/tasks/init"
workflow "github.com/karmada-io/karmada/operator/pkg/workflow"
)
var (
defaultCrdURL = "https://github.com/karmada-io/karmada/releases/download/%s/crds.tar.gz"
)
// InitOptions defines all the init workflow options.
type InitOptions struct {
Name string
Namespace string
Kubeconfig *rest.Config
KarmadaVersion string
CrdRemoteURL string
KarmadaDataDir string
Karmada *operatorv1alpha1.Karmada
}
// InitOpt defines a type of function to set InitOptions values.
type InitOpt func(opt *InitOptions)
var _ tasks.InitData = &initData{}
// initData defines all the runtime information used when ruing init workflow;
// this data is shared across all the tasks tha are included in the workflow.
type initData struct {
sync.Once
certs.CertStore
name string
namespace string
karmadaVersion *utilversion.Version
controlplaneConifig *rest.Config
remoteClient clientset.Interface
karmadaClient clientset.Interface
dnsDomain string
crdRemoteURL string
karmadaDataDir string
privateRegistry string
featureGates map[string]bool
components *operatorv1alpha1.KarmadaComponents
}
// NewInitJob initializes a job with list of init sub task. and build
// init runData object.
func NewInitJob(opt *InitOptions) *workflow.Job {
initJob := workflow.NewJob()
// add the all of tasks to the init job workflow.
initJob.AppendTask(tasks.NewPrepareCrdsTask())
initJob.AppendTask(tasks.NewCertTask())
initJob.AppendTask(tasks.NewNamespaceTask())
initJob.AppendTask(tasks.NewUploadKubeconfigTask())
initJob.AppendTask(tasks.NewUploadCertsTask())
initJob.AppendTask(tasks.NewEtcdTask())
initJob.AppendTask(tasks.NewKarmadaApiserverTask())
initJob.AppendTask(tasks.NewWaitApiserverTask())
initJob.AppendTask(tasks.NewKarmadaResourcesTask())
initJob.AppendTask(tasks.NewComponentTask())
initJob.AppendTask(tasks.NewWaitControlPlaneTask())
initJob.SetDataInitializer(func() (workflow.RunData, error) {
return newRunData(opt)
})
return initJob
}
func newRunData(opt *InitOptions) (*initData, error) {
// if there is no endpoint info, we are consider that the local cluster
// is remote cluster to install karmada.
var remoteClient clientset.Interface
if opt.Karmada.Spec.HostCluster.SecretRef == nil && len(opt.Karmada.Spec.HostCluster.APIEndpoint) == 0 {
client, err := clientset.NewForConfig(opt.Kubeconfig)
if err != nil {
return nil, fmt.Errorf("error when create cluster client to install karmada, err: %w", err)
}
remoteClient = client
}
var privateRegistry string
if opt.Karmada.Spec.PrivateRegistry != nil {
privateRegistry = opt.Karmada.Spec.PrivateRegistry.Registry
}
if len(opt.Name) == 0 || len(opt.Namespace) == 0 {
return nil, errors.New("unexpected empty name or namespace")
}
version, err := utilversion.ParseGeneric(opt.KarmadaVersion)
if err != nil {
return nil, fmt.Errorf("unexpected karmada invalid version %s", opt.KarmadaVersion)
}
if len(opt.CrdRemoteURL) > 0 {
if _, err := url.Parse(opt.CrdRemoteURL); err != nil {
return nil, fmt.Errorf("unexpected invalid crds remote url %s", opt.CrdRemoteURL)
}
}
if opt.Karmada.Spec.Components.Etcd.Local != nil && opt.Karmada.Spec.Components.Etcd.Local.CommonSettings.Replicas != nil {
replicas := *opt.Karmada.Spec.Components.Etcd.Local.CommonSettings.Replicas
if (replicas % 2) == 0 {
klog.Warningf("invalid etcd replicas %d, expected an odd number", replicas)
}
}
// TODO: Verify whether important values of initData is valid
return &initData{
name: opt.Name,
namespace: opt.Namespace,
karmadaVersion: version,
remoteClient: remoteClient,
crdRemoteURL: opt.CrdRemoteURL,
karmadaDataDir: opt.KarmadaDataDir,
privateRegistry: privateRegistry,
components: opt.Karmada.Spec.Components,
featureGates: opt.Karmada.Spec.FeatureGates,
dnsDomain: *opt.Karmada.Spec.HostCluster.Networking.DNSDomain,
CertStore: certs.NewCertStore(),
}, nil
}
func (data *initData) GetName() string {
return data.name
}
func (data *initData) GetNamespace() string {
return data.namespace
}
func (data *initData) RemoteClient() clientset.Interface {
return data.remoteClient
}
func (data *initData) KarmadaClient() clientset.Interface {
if data.karmadaClient == nil {
data.Once.Do(func() {
client, err := clientset.NewForConfig(data.controlplaneConifig)
if err != nil {
klog.Errorf("error when init karmada client, err: %w", err)
}
data.karmadaClient = client
})
}
return data.karmadaClient
}
func (data *initData) ControlplaneConifg() *rest.Config {
return data.controlplaneConifig
}
func (data *initData) SetControlplaneConifg(config *rest.Config) {
data.controlplaneConifig = config
}
func (data *initData) Components() *operatorv1alpha1.KarmadaComponents {
return data.components
}
func (data *initData) DataDir() string {
return data.karmadaDataDir
}
func (data *initData) CrdsRomoteURL() string {
return data.crdRemoteURL
}
func (data *initData) KarmadaVersion() string {
return data.karmadaVersion.String()
}
// NewJobInitOptions calls all of InitOpt func to initialize a InitOptions.
// if there is not InitOpt functions, it will return a default InitOptions.
func NewJobInitOptions(opts ...InitOpt) *InitOptions {
options := defaultJobInitOptions()
for _, c := range opts {
c(options)
}
return options
}
func defaultJobInitOptions() *InitOptions {
karmada := &operatorv1alpha1.Karmada{}
// set defaults for karmada.
operatorscheme.Scheme.Default(karmada)
return &InitOptions{
CrdRemoteURL: fmt.Sprintf(defaultCrdURL, constants.KarmadaDefaultVersion),
KarmadaVersion: constants.KarmadaDefaultVersion,
KarmadaDataDir: constants.KarmadaDataDir,
Karmada: karmada,
}
}
// NewInitOptWithKarmada returns a InitOpt function to initialize InitOptions with karmada resource
func NewInitOptWithKarmada(karmada *operatorv1alpha1.Karmada) InitOpt {
return func(opt *InitOptions) {
opt.Karmada = karmada
opt.Name = karmada.GetName()
opt.Namespace = karmada.GetNamespace()
}
}
// NewInitOptWithKubeconfig returns a InitOpt function to set kubeconfig to InitOptions with rest config
func NewInitOptWithKubeconfig(config *rest.Config) InitOpt {
return func(options *InitOptions) {
options.Kubeconfig = config
}
}