Files
kubevela/pkg/apiserver/sync/convert.go
2022-03-21 16:33:29 +08:00

240 lines
7.6 KiB
Go

/*
Copyright 2022 The KubeVela Authors.
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 sync
import (
"context"
"strconv"
"strings"
"time"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/workflow/step"
)
// ConvertFromCRComponent concerts Application CR Component object into velaux data store component
func ConvertFromCRComponent(appPrimaryKey string, component common.ApplicationComponent) (model.ApplicationComponent, error) {
bc := model.ApplicationComponent{
AppPrimaryKey: appPrimaryKey,
Name: component.Name,
Type: component.Type,
ExternalRevision: component.ExternalRevision,
DependsOn: component.DependsOn,
Inputs: component.Inputs,
Outputs: component.Outputs,
Scopes: component.Scopes,
Creator: model.AutoGenComp,
}
if component.Properties != nil {
properties, err := model.NewJSONStruct(component.Properties)
if err != nil {
return bc, err
}
bc.Properties = properties
}
for _, trait := range component.Traits {
properties, err := model.NewJSONStruct(trait.Properties)
if err != nil {
return bc, err
}
bc.Traits = append(bc.Traits, model.ApplicationTrait{CreateTime: time.Now(), UpdateTime: time.Now(), Properties: properties, Type: trait.Type, Alias: trait.Type, Description: "auto gen"})
}
return bc, nil
}
// ConvertFromCRPolicy converts Application CR Policy object into velaux data store policy
func ConvertFromCRPolicy(appPrimaryKey string, policyCR v1beta1.AppPolicy, creator string) (model.ApplicationPolicy, error) {
plc := model.ApplicationPolicy{
AppPrimaryKey: appPrimaryKey,
Name: policyCR.Name,
Type: policyCR.Type,
Creator: creator,
}
if policyCR.Properties != nil {
properties, err := model.NewJSONStruct(policyCR.Properties)
if err != nil {
return plc, err
}
plc.Properties = properties
}
return plc, nil
}
// ConvertFromCRWorkflow converts Application CR Workflow section into velaux data store workflow
func ConvertFromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string, app *v1beta1.Application) (model.Workflow, []v1beta1.WorkflowStep, error) {
dataWf := model.Workflow{
AppPrimaryKey: appPrimaryKey,
// every namespace has a synced env
EnvName: model.AutoGenEnvNamePrefix + app.Namespace,
// every application has a synced workflow
Name: model.AutoGenWorkflowNamePrefix + appPrimaryKey,
Alias: model.AutoGenWorkflowNamePrefix + app.Name,
Description: model.AutoGenDesc,
}
if app.Spec.Workflow == nil {
return dataWf, nil, nil
}
var steps []v1beta1.WorkflowStep
if app.Spec.Workflow.Ref != "" {
dataWf.Name = app.Spec.Workflow.Ref
wf := &v1alpha1.Workflow{}
if err := cli.Get(ctx, types.NamespacedName{Namespace: app.GetNamespace(), Name: app.Spec.Workflow.Ref}, wf); err != nil {
return dataWf, nil, err
}
steps = wf.Steps
} else {
steps = app.Spec.Workflow.Steps
}
for _, s := range steps {
if s.Properties == nil {
continue
}
properties, err := model.NewJSONStruct(s.Properties)
if err != nil {
return dataWf, nil, err
}
dataWf.Steps = append(dataWf.Steps, model.WorkflowStep{
Name: s.Name,
Type: s.Type,
Inputs: s.Inputs,
Outputs: s.Outputs,
DependsOn: s.DependsOn,
Properties: properties,
})
}
return dataWf, steps, nil
}
// ConvertFromCRTargets converts deployed Cluster/Namespace from Application CR Status into velaux data store
func ConvertFromCRTargets(targetApp *v1beta1.Application) []*model.Target {
var targets []*model.Target
nc := make(map[string]struct{})
for _, v := range targetApp.Status.AppliedResources {
var cluster = v.Cluster
if cluster == "" {
cluster = multicluster.ClusterLocalName
}
name := model.AutoGenTargetNamePrefix + cluster + "-" + v.Namespace
if _, ok := nc[name]; ok {
continue
}
nc[name] = struct{}{}
targets = append(targets, &model.Target{
Name: name,
Cluster: &model.ClusterTarget{
ClusterName: cluster,
Namespace: v.Namespace,
},
})
}
return targets
}
// ConvertApp2DatastoreApp will convert Application CR to datastore application related resources
func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.Application) (*model.DataStoreApp, error) {
cli := c.cli
appName := c.getAppMetaName(ctx, targetApp.Name, targetApp.Namespace)
project := model.DefaultInitName
if _, ok := targetApp.Labels[oam.LabelAddonName]; ok && strings.HasPrefix(targetApp.Name, "addon-") {
project = model.DefaultAddonProject
}
appMeta := &model.Application{
Name: appName,
Description: model.AutoGenDesc,
Alias: targetApp.Name,
Project: project,
Labels: map[string]string{
model.LabelSyncNamespace: targetApp.Namespace,
model.LabelSyncGeneration: strconv.FormatInt(targetApp.Generation, 10),
model.LabelSourceOfTruth: model.FromCR,
},
}
// 1. convert app meta and env
dsApp := &model.DataStoreApp{
AppMeta: appMeta,
Env: &model.Env{
Name: model.AutoGenEnvNamePrefix + targetApp.Namespace,
Namespace: targetApp.Namespace,
Description: model.AutoGenDesc,
Project: project,
Alias: model.AutoGenEnvNamePrefix + targetApp.Namespace,
},
Eb: &model.EnvBinding{
AppPrimaryKey: appMeta.PrimaryKey(),
Name: model.AutoGenEnvNamePrefix + targetApp.Namespace,
AppDeployName: appMeta.GetAppNameForSynced(),
},
}
// 2. convert component and trait
for _, cmp := range targetApp.Spec.Components {
compModel, err := ConvertFromCRComponent(appMeta.PrimaryKey(), cmp)
if err != nil {
return nil, err
}
dsApp.Comps = append(dsApp.Comps, &compModel)
}
// 3. convert workflow
wf, steps, err := ConvertFromCRWorkflow(ctx, cli, appMeta.PrimaryKey(), targetApp)
if err != nil {
return nil, err
}
dsApp.Workflow = &wf
// 4. convert policy, some policies are references in workflow step, we need to sync all the outside policy to make that work
var innerPlc = make(map[string]struct{})
for _, plc := range targetApp.Spec.Policies {
innerPlc[plc.Name] = struct{}{}
}
outsidePLC, err := step.LoadExternalPoliciesForWorkflow(ctx, cli, targetApp.Namespace, steps, targetApp.Spec.Policies)
if err != nil {
return nil, err
}
for _, plc := range outsidePLC {
plcModel, err := ConvertFromCRPolicy(appMeta.PrimaryKey(), plc, model.AutoGenRefPolicy)
if _, ok := innerPlc[plc.Name]; ok {
plcModel.Creator = model.AutoGenPolicy
}
if err != nil {
return nil, err
}
dsApp.Policies = append(dsApp.Policies, &plcModel)
}
// TODO(wonderflow): we don't sync targets as it can't be judged well in velaux env
// if we want to sync, we can extract targets from status, like below:
/*
dsApp.Targets = ConvertFromCRTargets(targetApp)
for _, t := range dsApp.Targets {
dsApp.Env.Targets = append(dsApp.Env.Targets, t.Name)
}
*/
return dsApp, nil
}