mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-14 21:36:54 +00:00
* Feat: add the rbac data model Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add some api about the project Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add CRUD about the project and the project user Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add CRUD about the role and perm check filter function Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: update swagger config Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add default roles and perm policies Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add perm check filter for all webservice Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: change the method that find project name Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: query applications and envs by user perm Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: support get login user info Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: change default permissions Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: change PermPolicy to Permission Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Feat: add some unit test and fix the e2e test error Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: change some comment word Signed-off-by: barnettZQG <barnett.zqg@gmail.com> * Fix: e2e api path error Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
1787 lines
60 KiB
Go
1787 lines
60 KiB
Go
/*
|
|
Copyright 2021 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 usecase
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "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/labels"
|
|
"k8s.io/apimachinery/pkg/selection"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"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/clients"
|
|
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
|
|
"github.com/oam-dev/kubevela/pkg/apiserver/log"
|
|
"github.com/oam-dev/kubevela/pkg/apiserver/model"
|
|
|
|
velatypes "github.com/oam-dev/kubevela/apis/types"
|
|
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
|
|
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
|
|
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
|
|
syncconvert "github.com/oam-dev/kubevela/pkg/apiserver/sync/convert"
|
|
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
|
utils2 "github.com/oam-dev/kubevela/pkg/utils"
|
|
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
|
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
|
)
|
|
|
|
// PolicyType build-in policy type
|
|
type PolicyType string
|
|
|
|
const (
|
|
// EnvBindingPolicy Multiple environment distribution policy
|
|
EnvBindingPolicy PolicyType = "env-binding"
|
|
|
|
// EnvBindingPolicyDefaultName default policy name
|
|
EnvBindingPolicyDefaultName string = "env-bindings"
|
|
|
|
defaultTokenLen int = 16
|
|
)
|
|
|
|
// ApplicationUsecase application usecase
|
|
type ApplicationUsecase interface {
|
|
ListApplications(ctx context.Context, listOptions apisv1.ListApplicationOptions) ([]*apisv1.ApplicationBase, error)
|
|
GetApplication(ctx context.Context, appName string) (*model.Application, error)
|
|
GetApplicationStatus(ctx context.Context, app *model.Application, envName string) (*common.AppStatus, error)
|
|
DetailApplication(ctx context.Context, app *model.Application) (*apisv1.DetailApplicationResponse, error)
|
|
PublishApplicationTemplate(ctx context.Context, app *model.Application) (*apisv1.ApplicationTemplateBase, error)
|
|
CreateApplication(context.Context, apisv1.CreateApplicationRequest) (*apisv1.ApplicationBase, error)
|
|
UpdateApplication(context.Context, *model.Application, apisv1.UpdateApplicationRequest) (*apisv1.ApplicationBase, error)
|
|
DeleteApplication(ctx context.Context, app *model.Application) error
|
|
Deploy(ctx context.Context, app *model.Application, req apisv1.ApplicationDeployRequest) (*apisv1.ApplicationDeployResponse, error)
|
|
GetApplicationComponent(ctx context.Context, app *model.Application, componentName string) (*model.ApplicationComponent, error)
|
|
ListComponents(ctx context.Context, app *model.Application, op apisv1.ListApplicationComponentOptions) ([]*apisv1.ComponentBase, error)
|
|
CreateComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest) (*apisv1.ComponentBase, error)
|
|
DetailComponent(ctx context.Context, app *model.Application, componentName string) (*apisv1.DetailComponentResponse, error)
|
|
DeleteComponent(ctx context.Context, app *model.Application, component *model.ApplicationComponent) error
|
|
UpdateComponent(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.UpdateApplicationComponentRequest) (*apisv1.ComponentBase, error)
|
|
ListPolicies(ctx context.Context, app *model.Application) ([]*apisv1.PolicyBase, error)
|
|
CreatePolicy(ctx context.Context, app *model.Application, policy apisv1.CreatePolicyRequest) (*apisv1.PolicyBase, error)
|
|
DetailPolicy(ctx context.Context, app *model.Application, policyName string) (*apisv1.DetailPolicyResponse, error)
|
|
DeletePolicy(ctx context.Context, app *model.Application, policyName string) error
|
|
UpdatePolicy(ctx context.Context, app *model.Application, policyName string, policy apisv1.UpdatePolicyRequest) (*apisv1.DetailPolicyResponse, error)
|
|
CreateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.CreateApplicationTraitRequest) (*apisv1.ApplicationTrait, error)
|
|
DeleteApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string) error
|
|
UpdateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string, req apisv1.UpdateApplicationTraitRequest) (*apisv1.ApplicationTrait, error)
|
|
ListRevisions(ctx context.Context, appName, envName, status string, page, pageSize int) (*apisv1.ListRevisionsResponse, error)
|
|
DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error)
|
|
Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error)
|
|
ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error)
|
|
CompareAppWithLatestRevision(ctx context.Context, app *model.Application, compareReq apisv1.AppCompareReq) (*apisv1.AppCompareResponse, error)
|
|
ResetAppToLatestRevision(ctx context.Context, appName string) (*apisv1.AppResetResponse, error)
|
|
DryRunAppOrRevision(ctx context.Context, app *model.Application, dryRunReq apisv1.AppDryRunReq) (*apisv1.AppDryRunResponse, error)
|
|
CreateApplicationTrigger(ctx context.Context, app *model.Application, req apisv1.CreateApplicationTriggerRequest) (*apisv1.ApplicationTriggerBase, error)
|
|
ListApplicationTriggers(ctx context.Context, app *model.Application) ([]*apisv1.ApplicationTriggerBase, error)
|
|
DeleteApplicationTrigger(ctx context.Context, app *model.Application, triggerName string) error
|
|
}
|
|
|
|
type applicationUsecaseImpl struct {
|
|
ds datastore.DataStore
|
|
kubeClient client.Client
|
|
apply apply.Applicator
|
|
workflowUsecase WorkflowUsecase
|
|
envUsecase EnvUsecase
|
|
envBindingUsecase EnvBindingUsecase
|
|
targetUsecase TargetUsecase
|
|
definitionUsecase DefinitionUsecase
|
|
projectUsecase ProjectUsecase
|
|
}
|
|
|
|
// NewApplicationUsecase new application usecase
|
|
func NewApplicationUsecase(ds datastore.DataStore,
|
|
workflowUsecase WorkflowUsecase,
|
|
envBindingUsecase EnvBindingUsecase,
|
|
envUsecase EnvUsecase,
|
|
targetUsecase TargetUsecase,
|
|
definitionUsecase DefinitionUsecase,
|
|
projectUsecase ProjectUsecase,
|
|
) ApplicationUsecase {
|
|
kubecli, err := clients.GetKubeClient()
|
|
if err != nil {
|
|
log.Logger.Fatalf("get kube client failure %s", err.Error())
|
|
}
|
|
return &applicationUsecaseImpl{
|
|
ds: ds,
|
|
workflowUsecase: workflowUsecase,
|
|
envBindingUsecase: envBindingUsecase,
|
|
targetUsecase: targetUsecase,
|
|
kubeClient: kubecli,
|
|
apply: apply.NewAPIApplicator(kubecli),
|
|
definitionUsecase: definitionUsecase,
|
|
projectUsecase: projectUsecase,
|
|
envUsecase: envUsecase,
|
|
}
|
|
}
|
|
|
|
func listApp(ctx context.Context, ds datastore.DataStore, listOptions apisv1.ListApplicationOptions) ([]*model.Application, error) {
|
|
var app = model.Application{}
|
|
var err error
|
|
var envBinding []*apisv1.EnvBindingBase
|
|
if listOptions.Env != "" || listOptions.TargetName != "" {
|
|
envBinding, err = listFullEnvBinding(ctx, ds, envListOption{})
|
|
if err != nil {
|
|
log.Logger.Errorf("list envbinding for list application in env %s err %v", utils2.Sanitize(listOptions.Env), err)
|
|
return nil, err
|
|
}
|
|
}
|
|
var filterOptions datastore.FilterOptions
|
|
if len(listOptions.Projects) > 0 {
|
|
filterOptions.In = append(filterOptions.In, datastore.InQueryOption{
|
|
Key: "project",
|
|
Values: listOptions.Projects,
|
|
})
|
|
}
|
|
entities, err := ds.List(ctx, &app, &datastore.ListOptions{FilterOptions: filterOptions})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var list []*model.Application
|
|
for _, entity := range entities {
|
|
appModel, ok := entity.(*model.Application)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if listOptions.Query != "" &&
|
|
!(strings.Contains(appModel.Alias, listOptions.Query) ||
|
|
strings.Contains(appModel.Name, listOptions.Query) ||
|
|
strings.Contains(appModel.Description, listOptions.Query)) {
|
|
continue
|
|
}
|
|
if listOptions.TargetName != "" {
|
|
targetIsContain, _ := CheckAppEnvBindingsContainTarget(envBinding, listOptions.TargetName)
|
|
if !targetIsContain {
|
|
continue
|
|
}
|
|
}
|
|
if len(envBinding) > 0 && listOptions.Env != "" {
|
|
check := func() bool {
|
|
for _, eb := range envBinding {
|
|
if eb.Name == listOptions.Env && appModel.PrimaryKey() == eb.AppDeployName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if !check() {
|
|
continue
|
|
}
|
|
}
|
|
list = append(list, appModel)
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
// ListApplications list applications
|
|
func (c *applicationUsecaseImpl) ListApplications(ctx context.Context, listOptions apisv1.ListApplicationOptions) ([]*apisv1.ApplicationBase, error) {
|
|
userName, ok := ctx.Value(&apisv1.CtxKeyUser).(string)
|
|
if !ok {
|
|
return nil, bcode.ErrUnauthorized
|
|
}
|
|
projects, err := c.projectUsecase.ListUserProjects(ctx, userName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var availableProjectNames []string
|
|
for _, project := range projects {
|
|
availableProjectNames = append(availableProjectNames, project.Name)
|
|
}
|
|
if len(availableProjectNames) == 0 {
|
|
return []*apisv1.ApplicationBase{}, nil
|
|
}
|
|
if len(listOptions.Projects) > 0 {
|
|
if !utils2.SliceIncludeSlice(availableProjectNames, listOptions.Projects) {
|
|
return []*apisv1.ApplicationBase{}, nil
|
|
}
|
|
}
|
|
if len(listOptions.Projects) == 0 {
|
|
listOptions.Projects = availableProjectNames
|
|
}
|
|
apps, err := listApp(ctx, c.ds, listOptions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var list []*apisv1.ApplicationBase
|
|
for _, app := range apps {
|
|
appBase := c.convertAppModelToBase(app, projects)
|
|
list = append(list, appBase)
|
|
}
|
|
sort.Slice(list, func(i, j int) bool {
|
|
return list[i].UpdateTime.Unix() > list[j].UpdateTime.Unix()
|
|
})
|
|
return list, nil
|
|
}
|
|
|
|
// GetApplication get application model
|
|
func (c *applicationUsecaseImpl) GetApplication(ctx context.Context, appName string) (*model.Application, error) {
|
|
var app = model.Application{
|
|
Name: appName,
|
|
}
|
|
if err := c.ds.Get(ctx, &app); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
return nil, bcode.ErrApplicationNotExist
|
|
}
|
|
return nil, err
|
|
}
|
|
return &app, nil
|
|
}
|
|
|
|
// DetailApplication detail application info
|
|
func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *model.Application) (*apisv1.DetailApplicationResponse, error) {
|
|
var project *apisv1.ProjectBase
|
|
if app.Project != "" {
|
|
var err error
|
|
project, err = c.projectUsecase.DetailProject(ctx, app.Project)
|
|
if err != nil {
|
|
return nil, bcode.ErrProjectIsNotExist
|
|
}
|
|
}
|
|
base := c.convertAppModelToBase(app, []*apisv1.ProjectBase{project})
|
|
policies, err := c.queryApplicationPolicies(ctx, app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
componentNum, err := c.ds.Count(ctx, &model.ApplicationComponent{AppPrimaryKey: app.PrimaryKey()}, &datastore.FilterOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
envBindings, err := c.envBindingUsecase.GetEnvBindings(ctx, app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var policyNames []string
|
|
var envBindingNames []string
|
|
for _, p := range policies {
|
|
policyNames = append(policyNames, p.Name)
|
|
}
|
|
for _, e := range envBindings {
|
|
envBindingNames = append(envBindingNames, e.Name)
|
|
}
|
|
|
|
var detail = &apisv1.DetailApplicationResponse{
|
|
ApplicationBase: *base,
|
|
Policies: policyNames,
|
|
EnvBindings: envBindingNames,
|
|
ResourceInfo: apisv1.ApplicationResourceInfo{
|
|
ComponentNum: componentNum,
|
|
},
|
|
ApplicationType: func() string {
|
|
if GetSuitableDeployWay(ctx, c.kubeClient, c.ds, app) == DeployCloudResource {
|
|
return "cloud"
|
|
}
|
|
return "common"
|
|
}(),
|
|
}
|
|
return detail, nil
|
|
}
|
|
|
|
// GetApplicationStatus get application status from controller cluster
|
|
func (c *applicationUsecaseImpl) GetApplicationStatus(ctx context.Context, appmodel *model.Application, envName string) (*common.AppStatus, error) {
|
|
var app v1beta1.Application
|
|
env, err := c.envUsecase.GetEnv(ctx, envName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = c.kubeClient.Get(ctx, types.NamespacedName{Namespace: env.Namespace, Name: appmodel.GetAppNameForSynced()}, &app)
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
if !app.DeletionTimestamp.IsZero() {
|
|
app.Status.Phase = "deleting"
|
|
}
|
|
return &app.Status, nil
|
|
}
|
|
|
|
// GetApplicationCR get application CR in cluster
|
|
func (c *applicationUsecaseImpl) GetApplicationCR(ctx context.Context, appModel *model.Application) (*v1beta1.ApplicationList, error) {
|
|
var apps v1beta1.ApplicationList
|
|
if appModel.IsSynced() {
|
|
var app v1beta1.Application
|
|
err := c.kubeClient.Get(ctx, types.NamespacedName{Namespace: appModel.GetAppNamespaceForSynced(), Name: appModel.GetAppNameForSynced()}, &app)
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return nil, err
|
|
}
|
|
if err == nil {
|
|
apps.Items = append(apps.Items, app)
|
|
return &apps, nil
|
|
}
|
|
}
|
|
selector := labels.NewSelector()
|
|
re, err := labels.NewRequirement(oam.AnnotationAppName, selection.Equals, []string{appModel.GetAppNameForSynced()})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
selector = selector.Add(*re)
|
|
err = c.kubeClient.List(ctx, &apps, &client.ListOptions{
|
|
LabelSelector: selector,
|
|
})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return &apps, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &apps, nil
|
|
}
|
|
|
|
// PublishApplicationTemplate publish app template
|
|
func (c *applicationUsecaseImpl) PublishApplicationTemplate(ctx context.Context, app *model.Application) (*apisv1.ApplicationTemplateBase, error) {
|
|
// TODO:
|
|
return nil, nil
|
|
}
|
|
|
|
// CreateApplication create application
|
|
func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apisv1.CreateApplicationRequest) (*apisv1.ApplicationBase, error) {
|
|
application := model.Application{
|
|
Name: req.Name,
|
|
Alias: req.Alias,
|
|
Description: req.Description,
|
|
Icon: req.Icon,
|
|
Labels: req.Labels,
|
|
}
|
|
// check app name.
|
|
exist, err := c.ds.IsExist(ctx, &application)
|
|
if err != nil {
|
|
log.Logger.Errorf("check application name is exist failure %s", err.Error())
|
|
return nil, bcode.ErrApplicationExist
|
|
}
|
|
if exist {
|
|
return nil, bcode.ErrApplicationExist
|
|
}
|
|
// check project
|
|
project, err := c.projectUsecase.DetailProject(ctx, req.Project)
|
|
if err != nil {
|
|
return nil, bcode.ErrProjectIsNotExist
|
|
}
|
|
application.Project = project.Name
|
|
|
|
if req.Component != nil {
|
|
_, err = c.createComponent(ctx, &application, *req.Component, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// build-in create env binding, it must after component added
|
|
if len(req.EnvBinding) > 0 {
|
|
err := c.saveApplicationEnvBinding(ctx, application, req.EnvBinding)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := c.CreateApplicationTrigger(ctx, &application, apisv1.CreateApplicationTriggerRequest{
|
|
Name: fmt.Sprintf("%s-%s", application.Name, "default"),
|
|
PayloadType: model.PayloadTypeCustom,
|
|
Type: apisv1.TriggerTypeWebhook,
|
|
WorkflowName: convertWorkflowName(req.EnvBinding[0].Name),
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// add application to db.
|
|
if err := c.ds.Add(ctx, &application); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordExist) {
|
|
return nil, bcode.ErrApplicationExist
|
|
}
|
|
return nil, err
|
|
}
|
|
// render app base info.
|
|
base := c.convertAppModelToBase(&application, []*apisv1.ProjectBase{project})
|
|
return base, nil
|
|
}
|
|
|
|
// CreateApplicationTrigger create application trigger
|
|
func (c *applicationUsecaseImpl) CreateApplicationTrigger(ctx context.Context, app *model.Application, req apisv1.CreateApplicationTriggerRequest) (*apisv1.ApplicationTriggerBase, error) {
|
|
trigger := &model.ApplicationTrigger{
|
|
AppPrimaryKey: app.Name,
|
|
WorkflowName: req.WorkflowName,
|
|
Name: req.Name,
|
|
Alias: req.Alias,
|
|
Description: req.Description,
|
|
Type: req.Type,
|
|
PayloadType: req.PayloadType,
|
|
Token: genWebhookToken(),
|
|
}
|
|
if err := c.ds.Add(ctx, trigger); err != nil {
|
|
log.Logger.Errorf("failed to create application trigger, %s", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
return &apisv1.ApplicationTriggerBase{
|
|
WorkflowName: req.WorkflowName,
|
|
Name: req.Name,
|
|
Alias: req.Alias,
|
|
Description: req.Description,
|
|
Type: req.Type,
|
|
PayloadType: req.PayloadType,
|
|
Token: trigger.Token,
|
|
ComponentName: req.ComponentName,
|
|
CreateTime: trigger.CreateTime,
|
|
UpdateTime: trigger.UpdateTime,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteApplicationTrigger delete application trigger
|
|
func (c *applicationUsecaseImpl) DeleteApplicationTrigger(ctx context.Context, app *model.Application, token string) error {
|
|
trigger := model.ApplicationTrigger{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Token: token,
|
|
}
|
|
if err := c.ds.Delete(ctx, &trigger); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
return bcode.ErrApplicationTriggerNotExist
|
|
}
|
|
log.Logger.Warnf("delete app trigger failure %s", err.Error())
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListApplicationTrigger list application triggers
|
|
func (c *applicationUsecaseImpl) ListApplicationTriggers(ctx context.Context, app *model.Application) ([]*apisv1.ApplicationTriggerBase, error) {
|
|
trigger := &model.ApplicationTrigger{
|
|
AppPrimaryKey: app.Name,
|
|
}
|
|
triggers, err := c.ds.List(ctx, trigger, &datastore.ListOptions{
|
|
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}},
|
|
)
|
|
if err != nil {
|
|
log.Logger.Errorf("failed to list application triggers, %s", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
resp := []*apisv1.ApplicationTriggerBase{}
|
|
for _, raw := range triggers {
|
|
trigger, ok := raw.(*model.ApplicationTrigger)
|
|
if ok {
|
|
resp = append(resp, &apisv1.ApplicationTriggerBase{
|
|
WorkflowName: trigger.WorkflowName,
|
|
Name: trigger.Name,
|
|
Alias: trigger.Alias,
|
|
Description: trigger.Description,
|
|
Type: trigger.Type,
|
|
PayloadType: trigger.PayloadType,
|
|
Token: trigger.Token,
|
|
UpdateTime: trigger.UpdateTime,
|
|
CreateTime: trigger.CreateTime,
|
|
})
|
|
}
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) genPolicyByEnv(ctx context.Context, app *model.Application, envName string, components []*model.ApplicationComponent) (v1beta1.AppPolicy, error) {
|
|
appPolicy := v1beta1.AppPolicy{}
|
|
envBinding, err := c.envBindingUsecase.GetEnvBinding(ctx, app, envName)
|
|
if err != nil {
|
|
return appPolicy, err
|
|
}
|
|
appPolicy.Name = genPolicyName(envBinding.Name)
|
|
appPolicy.Type = string(EnvBindingPolicy)
|
|
env, err := c.envUsecase.GetEnv(ctx, envName)
|
|
if err != nil {
|
|
return appPolicy, err
|
|
}
|
|
var envBindingSpec v1alpha1.EnvBindingSpec
|
|
for _, targetName := range env.Targets {
|
|
target, err := c.targetUsecase.GetTarget(ctx, targetName)
|
|
if err != nil || target == nil {
|
|
return appPolicy, bcode.ErrFoundEnvbindingDeliveryTarget
|
|
}
|
|
envBindingSpec.Envs = append(envBindingSpec.Envs, c.createTargetClusterEnv(ctx, envBinding, env, target, components))
|
|
}
|
|
properties, err := model.NewJSONStructByStruct(envBindingSpec)
|
|
if err != nil {
|
|
return appPolicy, bcode.ErrInvalidProperties
|
|
}
|
|
appPolicy.Properties = properties.RawExtension()
|
|
return appPolicy, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) saveApplicationEnvBinding(ctx context.Context, app model.Application, envBindings []*apisv1.EnvBinding) error {
|
|
err := c.envBindingUsecase.BatchCreateEnvBinding(ctx, &app, envBindings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) UpdateApplication(ctx context.Context, app *model.Application, req apisv1.UpdateApplicationRequest) (*apisv1.ApplicationBase, error) {
|
|
var project *apisv1.ProjectBase
|
|
if app.Project != "" {
|
|
var err error
|
|
project, err = c.projectUsecase.DetailProject(ctx, app.Project)
|
|
if err != nil {
|
|
return nil, bcode.ErrProjectIsNotExist
|
|
}
|
|
}
|
|
app.Alias = req.Alias
|
|
app.Description = req.Description
|
|
app.Labels = req.Labels
|
|
app.Icon = req.Icon
|
|
if err := c.ds.Put(ctx, app); err != nil {
|
|
return nil, err
|
|
}
|
|
return c.convertAppModelToBase(app, []*apisv1.ProjectBase{project}), nil
|
|
}
|
|
|
|
// ListRecords list application record
|
|
func (c *applicationUsecaseImpl) ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error) {
|
|
var record = model.WorkflowRecord{
|
|
AppPrimaryKey: appName,
|
|
Finished: "false",
|
|
}
|
|
records, err := c.ds.List(ctx, &record, &datastore.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(records) == 0 {
|
|
record.Finished = "true"
|
|
records, err = c.ds.List(ctx, &record, &datastore.ListOptions{
|
|
Page: 1,
|
|
PageSize: 1,
|
|
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
resp := &apisv1.ListWorkflowRecordsResponse{
|
|
Records: []apisv1.WorkflowRecord{},
|
|
}
|
|
for _, raw := range records {
|
|
record, ok := raw.(*model.WorkflowRecord)
|
|
if ok {
|
|
resp.Records = append(resp.Records, *convertFromRecordModel(record))
|
|
}
|
|
}
|
|
resp.Total = int64(len(records))
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) ListComponents(ctx context.Context, app *model.Application, op apisv1.ListApplicationComponentOptions) ([]*apisv1.ComponentBase, error) {
|
|
var component = model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
}
|
|
components, err := c.ds.List(ctx, &component, &datastore.ListOptions{SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var list []*apisv1.ComponentBase
|
|
var main *apisv1.ComponentBase
|
|
for _, component := range components {
|
|
pm := component.(*model.ApplicationComponent)
|
|
if !pm.Main {
|
|
list = append(list, convertComponentModelToBase(pm))
|
|
} else {
|
|
main = convertComponentModelToBase(pm)
|
|
}
|
|
}
|
|
// the main component must be first
|
|
if main != nil {
|
|
list = append([]*apisv1.ComponentBase{main}, list...)
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
// DetailComponent detail app component
|
|
// TODO: Add status data about the component.
|
|
func (c *applicationUsecaseImpl) DetailComponent(ctx context.Context, app *model.Application, compName string) (*apisv1.DetailComponentResponse, error) {
|
|
var component = model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: compName,
|
|
}
|
|
err := c.ds.Get(ctx, &component)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var cd v1beta1.ComponentDefinition
|
|
if err := c.kubeClient.Get(ctx, types.NamespacedName{Name: component.Type, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
|
|
log.Logger.Warnf("component definition %s get failure. %s", component.Type, err.Error())
|
|
}
|
|
|
|
return &apisv1.DetailComponentResponse{
|
|
ApplicationComponent: component,
|
|
Definition: cd.Spec,
|
|
}, nil
|
|
}
|
|
|
|
// ListPolicies list application policies
|
|
func (c *applicationUsecaseImpl) ListPolicies(ctx context.Context, app *model.Application) ([]*apisv1.PolicyBase, error) {
|
|
policies, err := c.queryApplicationPolicies(ctx, app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var list []*apisv1.PolicyBase
|
|
for _, policy := range policies {
|
|
list = append(list, convertPolicyModelToBase(policy))
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) queryApplicationPolicies(ctx context.Context, app *model.Application) (list []*model.ApplicationPolicy, err error) {
|
|
var policy = model.ApplicationPolicy{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
}
|
|
policies, err := c.ds.List(ctx, &policy, &datastore.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, policy := range policies {
|
|
pm := policy.(*model.ApplicationPolicy)
|
|
list = append(list, pm)
|
|
}
|
|
return
|
|
}
|
|
|
|
// DetailPolicy detail app policy
|
|
// TODO: Add status data about the policy.
|
|
func (c *applicationUsecaseImpl) DetailPolicy(ctx context.Context, app *model.Application, policyName string) (*apisv1.DetailPolicyResponse, error) {
|
|
var policy = model.ApplicationPolicy{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: policyName,
|
|
}
|
|
err := c.ds.Get(ctx, &policy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.DetailPolicyResponse{
|
|
PolicyBase: *convertPolicyModelToBase(&policy),
|
|
}, nil
|
|
}
|
|
|
|
// Deploy deploy app to cluster
|
|
// means to render oam application config and apply to cluster.
|
|
// An event record is generated for each deploy.
|
|
func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Application, req apisv1.ApplicationDeployRequest) (*apisv1.ApplicationDeployResponse, error) {
|
|
// TODO: rollback to handle all the error case
|
|
// step1: Render oam application
|
|
version := utils.GenerateVersion("")
|
|
oamApp, err := c.renderOAMApplication(ctx, app, req.WorkflowName, version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configByte, _ := yaml.Marshal(oamApp)
|
|
|
|
workflow, err := c.workflowUsecase.GetWorkflow(ctx, app, oamApp.Annotations[oam.AnnotationWorkflowName])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// step2: check and create deploy event
|
|
if !req.Force {
|
|
var lastVersion = model.ApplicationRevision{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
EnvName: workflow.EnvName,
|
|
}
|
|
list, err := c.ds.List(ctx, &lastVersion, &datastore.ListOptions{
|
|
PageSize: 1, Page: 1, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
|
|
if err != nil && !errors.Is(err, datastore.ErrRecordNotExist) {
|
|
log.Logger.Errorf("query app latest revision failure %s", err.Error())
|
|
return nil, bcode.ErrDeployConflict
|
|
}
|
|
if len(list) > 0 {
|
|
revision := list[0].(*model.ApplicationRevision)
|
|
var status string
|
|
if revision.Status == model.RevisionStatusRollback {
|
|
rollbackRevision := &model.ApplicationRevision{
|
|
AppPrimaryKey: revision.AppPrimaryKey,
|
|
Version: revision.RollbackVersion,
|
|
}
|
|
if err := c.ds.Get(ctx, rollbackRevision); err == nil {
|
|
status = rollbackRevision.Status
|
|
}
|
|
} else {
|
|
status = revision.Status
|
|
}
|
|
if status != model.RevisionStatusComplete && status != model.RevisionStatusTerminated {
|
|
log.Logger.Warnf("last app revision can not complete %s/%s", list[0].(*model.ApplicationRevision).AppPrimaryKey, list[0].(*model.ApplicationRevision).Version)
|
|
return nil, bcode.ErrDeployConflict
|
|
}
|
|
}
|
|
}
|
|
|
|
var appRevision = &model.ApplicationRevision{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Version: version,
|
|
ApplyAppConfig: string(configByte),
|
|
Status: model.RevisionStatusInit,
|
|
// TODO: Get user information from ctx and assign a value.
|
|
DeployUser: "",
|
|
Note: req.Note,
|
|
TriggerType: req.TriggerType,
|
|
WorkflowName: oamApp.Annotations[oam.AnnotationWorkflowName],
|
|
EnvName: workflow.EnvName,
|
|
CodeInfo: req.CodeInfo,
|
|
ImageInfo: req.ImageInfo,
|
|
}
|
|
if err := c.ds.Add(ctx, appRevision); err != nil {
|
|
return nil, err
|
|
}
|
|
// step3: check and create namespace
|
|
var namespace corev1.Namespace
|
|
if err := c.kubeClient.Get(ctx, types.NamespacedName{Name: oamApp.Namespace}, &namespace); apierrors.IsNotFound(err) {
|
|
namespace.Name = oamApp.Namespace
|
|
if err := c.kubeClient.Create(ctx, &namespace); err != nil {
|
|
log.Logger.Errorf("auto create namespace failure %s", err.Error())
|
|
return nil, bcode.ErrCreateNamespace
|
|
}
|
|
}
|
|
// step4: apply to controller cluster
|
|
err = c.apply.Apply(ctx, oamApp)
|
|
if err != nil {
|
|
appRevision.Status = model.RevisionStatusFail
|
|
appRevision.Reason = err.Error()
|
|
if err := c.ds.Put(ctx, appRevision); err != nil {
|
|
log.Logger.Warnf("update deploy event failure %s", err.Error())
|
|
}
|
|
|
|
log.Logger.Errorf("deploy app %s failure %s", app.PrimaryKey(), err.Error())
|
|
return nil, bcode.ErrDeployApplyFail
|
|
}
|
|
|
|
// step5: create workflow record
|
|
if err := c.workflowUsecase.CreateWorkflowRecord(ctx, app, oamApp, workflow); err != nil {
|
|
log.Logger.Warnf("create workflow record failure %s", err.Error())
|
|
}
|
|
|
|
// step6: update app revision status
|
|
appRevision.Status = model.RevisionStatusRunning
|
|
if err := c.ds.Put(ctx, appRevision); err != nil {
|
|
log.Logger.Warnf("update app revision failure %s", err.Error())
|
|
}
|
|
|
|
return &apisv1.ApplicationDeployResponse{
|
|
ApplicationRevisionBase: c.convertRevisionModelToBase(appRevision),
|
|
}, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, version string) (*v1beta1.Application, error) {
|
|
// Priority 1 uses the requested workflow as release .
|
|
// Priority 2 uses the default workflow as release .
|
|
var workflow *model.Workflow
|
|
var err error
|
|
if reqWorkflowName != "" {
|
|
workflow, err = c.workflowUsecase.GetWorkflow(ctx, appModel, reqWorkflowName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
workflow, err = c.workflowUsecase.GetApplicationDefaultWorkflow(ctx, appModel)
|
|
if err != nil && !errors.Is(err, bcode.ErrWorkflowNoDefault) {
|
|
return nil, err
|
|
}
|
|
}
|
|
if workflow == nil || workflow.EnvName == "" {
|
|
return nil, bcode.ErrWorkflowNotExist
|
|
}
|
|
env, err := c.envUsecase.GetEnv(ctx, workflow.EnvName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
labels := make(map[string]string)
|
|
for key, value := range appModel.Labels {
|
|
labels[key] = value
|
|
}
|
|
labels[oam.AnnotationAppName] = appModel.Name
|
|
|
|
var app = &v1beta1.Application{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Application",
|
|
APIVersion: "core.oam.dev/v1beta1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: appModel.Name,
|
|
Namespace: env.Namespace,
|
|
Labels: labels,
|
|
Annotations: map[string]string{
|
|
oam.AnnotationDeployVersion: version,
|
|
// publish version is the identifier of workflow record
|
|
oam.AnnotationPublishVersion: utils.GenerateVersion(reqWorkflowName),
|
|
oam.AnnotationAppName: appModel.Name,
|
|
oam.AnnotationAppAlias: appModel.Alias,
|
|
},
|
|
},
|
|
}
|
|
originalApp := &v1beta1.Application{}
|
|
if err := c.kubeClient.Get(ctx, types.NamespacedName{
|
|
Name: appModel.Name,
|
|
Namespace: env.Namespace,
|
|
}, originalApp); err == nil {
|
|
app.ResourceVersion = originalApp.ResourceVersion
|
|
}
|
|
|
|
var component = model.ApplicationComponent{
|
|
AppPrimaryKey: appModel.PrimaryKey(),
|
|
}
|
|
components, err := c.ds.List(ctx, &component, &datastore.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err != nil || len(components) == 0 {
|
|
return nil, bcode.ErrNoComponent
|
|
}
|
|
|
|
var policy = model.ApplicationPolicy{
|
|
AppPrimaryKey: appModel.PrimaryKey(),
|
|
}
|
|
policies, err := c.ds.List(ctx, &policy, &datastore.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var componentModels []*model.ApplicationComponent
|
|
for _, entity := range components {
|
|
component := entity.(*model.ApplicationComponent)
|
|
componentModels = append(componentModels, component)
|
|
var traits []common.ApplicationTrait
|
|
for _, trait := range component.Traits {
|
|
aTrait := common.ApplicationTrait{
|
|
Type: trait.Type,
|
|
}
|
|
if trait.Properties != nil {
|
|
aTrait.Properties = trait.Properties.RawExtension()
|
|
}
|
|
traits = append(traits, aTrait)
|
|
}
|
|
bc := common.ApplicationComponent{
|
|
Name: component.Name,
|
|
Type: component.Type,
|
|
ExternalRevision: component.ExternalRevision,
|
|
DependsOn: component.DependsOn,
|
|
Inputs: component.Inputs,
|
|
Outputs: component.Outputs,
|
|
Traits: traits,
|
|
Scopes: component.Scopes,
|
|
Properties: component.Properties.RawExtension(),
|
|
}
|
|
if component.Properties != nil {
|
|
bc.Properties = component.Properties.RawExtension()
|
|
}
|
|
app.Spec.Components = append(app.Spec.Components, bc)
|
|
}
|
|
|
|
for _, entity := range policies {
|
|
policy := entity.(*model.ApplicationPolicy)
|
|
apolicy := v1beta1.AppPolicy{
|
|
Name: policy.Name,
|
|
Type: policy.Type,
|
|
}
|
|
if policy.Properties != nil {
|
|
apolicy.Properties = policy.Properties.RawExtension()
|
|
}
|
|
app.Spec.Policies = append(app.Spec.Policies, apolicy)
|
|
}
|
|
if workflow.EnvName != "" {
|
|
envPolicy, err := c.genPolicyByEnv(ctx, appModel, workflow.EnvName, componentModels)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
app.Spec.Policies = append(app.Spec.Policies, envPolicy)
|
|
}
|
|
app.Annotations[oam.AnnotationWorkflowName] = workflow.Name
|
|
var steps []v1beta1.WorkflowStep
|
|
for _, step := range workflow.Steps {
|
|
var wstep = v1beta1.WorkflowStep{
|
|
Name: step.Name,
|
|
Type: step.Type,
|
|
Inputs: step.Inputs,
|
|
Outputs: step.Outputs,
|
|
}
|
|
if step.Properties != nil {
|
|
wstep.Properties = step.Properties.RawExtension()
|
|
}
|
|
steps = append(steps, wstep)
|
|
}
|
|
app.Spec.Workflow = &v1beta1.Workflow{
|
|
Steps: steps,
|
|
}
|
|
|
|
return app, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) convertAppModelToBase(app *model.Application, projects []*apisv1.ProjectBase) *apisv1.ApplicationBase {
|
|
appBase := &apisv1.ApplicationBase{
|
|
Name: app.Name,
|
|
Alias: app.Alias,
|
|
CreateTime: app.CreateTime,
|
|
UpdateTime: app.UpdateTime,
|
|
Description: app.Description,
|
|
Icon: app.Icon,
|
|
Labels: app.Labels,
|
|
Project: &apisv1.ProjectBase{Name: app.Project},
|
|
}
|
|
if app.IsSynced() {
|
|
appBase.ReadOnly = true
|
|
}
|
|
for _, project := range projects {
|
|
if project.Name == app.Project {
|
|
appBase.Project = project
|
|
}
|
|
}
|
|
return appBase
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) convertRevisionModelToBase(revision *model.ApplicationRevision) apisv1.ApplicationRevisionBase {
|
|
return apisv1.ApplicationRevisionBase{
|
|
Version: revision.Version,
|
|
Status: revision.Status,
|
|
Reason: revision.Reason,
|
|
DeployUser: revision.DeployUser,
|
|
Note: revision.Note,
|
|
TriggerType: revision.TriggerType,
|
|
CreateTime: revision.CreateTime,
|
|
EnvName: revision.EnvName,
|
|
CodeInfo: revision.CodeInfo,
|
|
ImageInfo: revision.ImageInfo,
|
|
}
|
|
}
|
|
|
|
// DeleteApplication delete application
|
|
func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *model.Application) error {
|
|
crs, err := c.GetApplicationCR(ctx, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(crs.Items) > 0 {
|
|
return bcode.ErrApplicationRefusedDelete
|
|
}
|
|
// query all components to deleted
|
|
components, err := c.ListComponents(ctx, app, apisv1.ListApplicationComponentOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// query all policies to deleted
|
|
policies, err := c.ListPolicies(ctx, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var revision = model.ApplicationRevision{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
}
|
|
revisions, err := c.ds.List(ctx, &revision, &datastore.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
triggers, err := c.ListApplicationTriggers(ctx, app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// delete workflow
|
|
if err := c.workflowUsecase.DeleteWorkflowByApp(ctx, app); err != nil && !errors.Is(err, bcode.ErrWorkflowNotExist) {
|
|
log.Logger.Errorf("delete workflow %s failure %s", app.Name, err.Error())
|
|
}
|
|
|
|
for _, component := range components {
|
|
err := c.ds.Delete(ctx, &model.ApplicationComponent{AppPrimaryKey: app.PrimaryKey(), Name: component.Name})
|
|
if err != nil && !errors.Is(err, datastore.ErrRecordNotExist) {
|
|
log.Logger.Errorf("delete component %s in app %s failure %s", component.Name, app.Name, err.Error())
|
|
}
|
|
}
|
|
|
|
for _, policy := range policies {
|
|
err := c.ds.Delete(ctx, &model.ApplicationPolicy{AppPrimaryKey: app.PrimaryKey(), Name: policy.Name})
|
|
if err != nil && errors.Is(err, datastore.ErrRecordNotExist) {
|
|
log.Logger.Errorf("delete policy %s in app %s failure %s", policy.Name, app.Name, err.Error())
|
|
}
|
|
}
|
|
|
|
for _, entity := range revisions {
|
|
revision := entity.(*model.ApplicationRevision)
|
|
if err := c.ds.Delete(ctx, &model.ApplicationRevision{AppPrimaryKey: app.PrimaryKey(), Version: revision.Version}); err != nil {
|
|
log.Logger.Errorf("delete revision %s in app %s failure %s", revision.Version, app.Name, err.Error())
|
|
}
|
|
}
|
|
|
|
for _, trigger := range triggers {
|
|
if err := c.ds.Delete(ctx, &model.ApplicationTrigger{AppPrimaryKey: app.PrimaryKey(), Name: trigger.Name, Token: trigger.Token}); err != nil {
|
|
log.Logger.Errorf("delete trigger %s in app %s failure %s", trigger.Name, app.Name, err.Error())
|
|
}
|
|
}
|
|
|
|
if err := c.envBindingUsecase.BatchDeleteEnvBinding(ctx, app); err != nil {
|
|
log.Logger.Errorf("delete envbindings in app %s failure %s", app.Name, err.Error())
|
|
}
|
|
|
|
return c.ds.Delete(ctx, app)
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) GetApplicationComponent(ctx context.Context, app *model.Application, componentName string) (*model.ApplicationComponent, error) {
|
|
var component = model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: componentName,
|
|
}
|
|
err := c.ds.Get(ctx, &component)
|
|
if err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
return nil, bcode.ErrApplicationComponentNotExist
|
|
}
|
|
return nil, err
|
|
}
|
|
return &component, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) UpdateComponent(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.UpdateApplicationComponentRequest) (*apisv1.ComponentBase, error) {
|
|
if req.Alias != nil {
|
|
component.Alias = *req.Alias
|
|
}
|
|
if req.Description != nil {
|
|
component.Description = *req.Description
|
|
}
|
|
if req.DependsOn != nil {
|
|
component.DependsOn = *req.DependsOn
|
|
}
|
|
if req.Icon != nil {
|
|
component.Icon = *req.Icon
|
|
}
|
|
if req.Labels != nil {
|
|
component.Labels = *req.Labels
|
|
}
|
|
if req.Properties != nil {
|
|
properties, err := model.NewJSONStructByString(*req.Properties)
|
|
if err != nil {
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
component.Properties = properties
|
|
}
|
|
if err := c.ds.Put(ctx, component); err != nil {
|
|
return nil, err
|
|
}
|
|
return convertComponentModelToBase(component), nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) createComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest, main bool) (*apisv1.ComponentBase, error) {
|
|
componentModel := model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Description: com.Description,
|
|
Labels: com.Labels,
|
|
Icon: com.Icon,
|
|
Creator: "", // TODO: Get user information from ctx and assign a value.
|
|
Name: com.Name,
|
|
Type: com.ComponentType,
|
|
DependsOn: com.DependsOn,
|
|
Inputs: com.Inputs,
|
|
Outputs: com.Outputs,
|
|
Alias: com.Alias,
|
|
Main: main,
|
|
}
|
|
var traits []model.ApplicationTrait
|
|
var traitTypes = make(map[string]bool)
|
|
for _, trait := range com.Traits {
|
|
if _, ok := traitTypes[trait.Type]; ok {
|
|
return nil, bcode.ErrTraitAlreadyExist
|
|
}
|
|
traitTypes[trait.Type] = true
|
|
properties, err := model.NewJSONStructByString(trait.Properties)
|
|
if err != nil {
|
|
log.Logger.Errorf("new trait failure,%s", err.Error())
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
traits = append(traits, model.ApplicationTrait{
|
|
Alias: trait.Alias,
|
|
Description: trait.Description,
|
|
Type: trait.Type,
|
|
Properties: properties,
|
|
CreateTime: time.Now(),
|
|
UpdateTime: time.Now(),
|
|
})
|
|
}
|
|
componentModel.Traits = traits
|
|
properties, err := model.NewJSONStructByString(com.Properties)
|
|
if err != nil {
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
componentModel.Properties = properties
|
|
// create default trait for component
|
|
if len(componentModel.Traits) == 0 {
|
|
c.initCreateDefaultTrait(&componentModel)
|
|
}
|
|
|
|
if err := c.ds.Add(ctx, &componentModel); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordExist) {
|
|
return nil, bcode.ErrApplicationComponentExist
|
|
}
|
|
log.Logger.Warnf("add component for app %s failure %s", utils2.Sanitize(app.PrimaryKey()), err.Error())
|
|
return nil, err
|
|
}
|
|
return convertComponentModelToBase(&componentModel), nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) CreateComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest) (*apisv1.ComponentBase, error) {
|
|
return c.createComponent(ctx, app, com, false)
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) initCreateDefaultTrait(component *model.ApplicationComponent) {
|
|
replicationTrait := model.ApplicationTrait{
|
|
Alias: "Set Replicas",
|
|
Type: "scaler",
|
|
Description: "Adjust the number of application instance.",
|
|
Properties: &model.JSONStruct{
|
|
"replicas": 1,
|
|
},
|
|
CreateTime: time.Now(),
|
|
UpdateTime: time.Now(),
|
|
}
|
|
var initTraits = []model.ApplicationTrait{}
|
|
if component.Type == "webservice" {
|
|
initTraits = append(initTraits, replicationTrait)
|
|
}
|
|
component.Traits = initTraits
|
|
}
|
|
|
|
func convertComponentModelToBase(componentModel *model.ApplicationComponent) *apisv1.ComponentBase {
|
|
if componentModel == nil {
|
|
return nil
|
|
}
|
|
return &apisv1.ComponentBase{
|
|
Name: componentModel.Name,
|
|
Alias: componentModel.Alias,
|
|
Description: componentModel.Description,
|
|
Labels: componentModel.Labels,
|
|
ComponentType: componentModel.Type,
|
|
Icon: componentModel.Icon,
|
|
DependsOn: componentModel.DependsOn,
|
|
Inputs: componentModel.Inputs,
|
|
Outputs: componentModel.Outputs,
|
|
Creator: componentModel.Creator,
|
|
Main: componentModel.Main,
|
|
CreateTime: componentModel.CreateTime,
|
|
UpdateTime: componentModel.UpdateTime,
|
|
Traits: func() (traits []*apisv1.ApplicationTrait) {
|
|
for _, trait := range componentModel.Traits {
|
|
traits = append(traits, &apisv1.ApplicationTrait{
|
|
Type: trait.Type,
|
|
Properties: trait.Properties,
|
|
Alias: trait.Alias,
|
|
Description: trait.Description,
|
|
CreateTime: trait.CreateTime,
|
|
UpdateTime: trait.UpdateTime,
|
|
})
|
|
}
|
|
return
|
|
}(),
|
|
}
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) DeleteComponent(ctx context.Context, app *model.Application, component *model.ApplicationComponent) error {
|
|
if component.Main {
|
|
return bcode.ErrApplicationComponentNotAllowDelete
|
|
}
|
|
if err := c.ds.Delete(ctx, component); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
return bcode.ErrApplicationComponentNotExist
|
|
}
|
|
log.Logger.Warnf("delete app component %s failure %s", app.PrimaryKey(), err.Error())
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) CreatePolicy(ctx context.Context, app *model.Application, createpolicy apisv1.CreatePolicyRequest) (*apisv1.PolicyBase, error) {
|
|
policyModel := model.ApplicationPolicy{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Description: createpolicy.Description,
|
|
// TODO: Get user information from ctx and assign a value.
|
|
Creator: "",
|
|
Name: createpolicy.Name,
|
|
Type: createpolicy.Type,
|
|
}
|
|
properties, err := model.NewJSONStructByString(createpolicy.Properties)
|
|
if err != nil {
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
policyModel.Properties = properties
|
|
if err := c.ds.Add(ctx, &policyModel); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordExist) {
|
|
return nil, bcode.ErrApplicationPolicyExist
|
|
}
|
|
log.Logger.Warnf("add policy for app %s failure %s", app.PrimaryKey(), err.Error())
|
|
return nil, err
|
|
}
|
|
return &apisv1.PolicyBase{
|
|
Name: policyModel.Name,
|
|
Description: policyModel.Description,
|
|
Type: policyModel.Type,
|
|
Creator: policyModel.Creator,
|
|
CreateTime: policyModel.CreateTime,
|
|
UpdateTime: policyModel.UpdateTime,
|
|
Properties: policyModel.Properties,
|
|
}, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) DeletePolicy(ctx context.Context, app *model.Application, policyName string) error {
|
|
var policy = model.ApplicationPolicy{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: policyName,
|
|
}
|
|
if err := c.ds.Delete(ctx, &policy); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
return bcode.ErrApplicationPolicyNotExist
|
|
}
|
|
log.Logger.Warnf("delete app policy %s failure %s", app.PrimaryKey(), err.Error())
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) UpdatePolicy(ctx context.Context, app *model.Application, policyName string, policyUpdate apisv1.UpdatePolicyRequest) (*apisv1.DetailPolicyResponse, error) {
|
|
var policy = model.ApplicationPolicy{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: policyName,
|
|
}
|
|
err := c.ds.Get(ctx, &policy)
|
|
if err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
return nil, bcode.ErrApplicationPolicyNotExist
|
|
}
|
|
log.Logger.Warnf("update app policy %s failure %s", app.PrimaryKey(), err.Error())
|
|
return nil, err
|
|
}
|
|
policy.Type = policyUpdate.Type
|
|
properties, err := model.NewJSONStructByString(policyUpdate.Properties)
|
|
if err != nil {
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
policy.Properties = properties
|
|
policy.Description = policyUpdate.Description
|
|
|
|
if err := c.ds.Put(ctx, &policy); err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.DetailPolicyResponse{
|
|
PolicyBase: *convertPolicyModelToBase(&policy),
|
|
}, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) CreateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.CreateApplicationTraitRequest) (*apisv1.ApplicationTrait, error) {
|
|
var comp = model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: component.Name,
|
|
}
|
|
if err := c.ds.Get(ctx, &comp); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, trait := range comp.Traits {
|
|
if trait.Type == req.Type {
|
|
return nil, bcode.ErrTraitAlreadyExist
|
|
}
|
|
}
|
|
properties, err := model.NewJSONStructByString(req.Properties)
|
|
if err != nil {
|
|
log.Logger.Errorf("new trait failure,%s", err.Error())
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
trait := model.ApplicationTrait{CreateTime: time.Now(), Type: req.Type, Properties: properties, Alias: req.Alias, Description: req.Description}
|
|
comp.Traits = append(comp.Traits, trait)
|
|
if err := c.ds.Put(ctx, &comp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.ApplicationTrait{Type: trait.Type, Properties: properties, Alias: req.Alias, Description: req.Description, CreateTime: trait.CreateTime, UpdateTime: trait.UpdateTime}, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) DeleteApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string) error {
|
|
var comp = model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: component.Name,
|
|
}
|
|
if err := c.ds.Get(ctx, &comp); err != nil {
|
|
return err
|
|
}
|
|
for i, trait := range comp.Traits {
|
|
if trait.Type == traitType {
|
|
comp.Traits = append(comp.Traits[:i], comp.Traits[i+1:]...)
|
|
if err := c.ds.Put(ctx, &comp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return bcode.ErrTraitNotExist
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) UpdateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string, req apisv1.UpdateApplicationTraitRequest) (*apisv1.ApplicationTrait, error) {
|
|
var comp = model.ApplicationComponent{
|
|
AppPrimaryKey: app.PrimaryKey(),
|
|
Name: component.Name,
|
|
}
|
|
if err := c.ds.Get(ctx, &comp); err != nil {
|
|
return nil, err
|
|
}
|
|
for i, trait := range comp.Traits {
|
|
if trait.Type == traitType {
|
|
properties, err := model.NewJSONStructByString(req.Properties)
|
|
if err != nil {
|
|
log.Logger.Errorf("update trait failure,%s", err.Error())
|
|
return nil, bcode.ErrInvalidProperties
|
|
}
|
|
updatedTrait := model.ApplicationTrait{CreateTime: trait.CreateTime, UpdateTime: time.Now(), Properties: properties, Type: traitType, Alias: req.Alias, Description: req.Description}
|
|
comp.Traits[i] = updatedTrait
|
|
if err := c.ds.Put(ctx, &comp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.ApplicationTrait{Type: trait.Type, Properties: properties,
|
|
Alias: updatedTrait.Alias, Description: updatedTrait.Description, CreateTime: updatedTrait.CreateTime, UpdateTime: updatedTrait.UpdateTime}, nil
|
|
}
|
|
}
|
|
return nil, bcode.ErrTraitNotExist
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) ListRevisions(ctx context.Context, appName, envName, status string, page, pageSize int) (*apisv1.ListRevisionsResponse, error) {
|
|
var revision = model.ApplicationRevision{
|
|
AppPrimaryKey: appName,
|
|
}
|
|
if envName != "" {
|
|
revision.EnvName = envName
|
|
}
|
|
if status != "" {
|
|
revision.Status = status
|
|
}
|
|
|
|
revisions, err := c.ds.List(ctx, &revision, &datastore.ListOptions{
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp := &apisv1.ListRevisionsResponse{
|
|
Revisions: []apisv1.ApplicationRevisionBase{},
|
|
}
|
|
for _, raw := range revisions {
|
|
r, ok := raw.(*model.ApplicationRevision)
|
|
if ok {
|
|
resp.Revisions = append(resp.Revisions, c.convertRevisionModelToBase(r))
|
|
}
|
|
}
|
|
count, err := c.ds.Count(ctx, &revision, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp.Total = count
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) DetailRevision(ctx context.Context, appName, revisionVersion string) (*apisv1.DetailRevisionResponse, error) {
|
|
var revision = model.ApplicationRevision{
|
|
AppPrimaryKey: appName,
|
|
Version: revisionVersion,
|
|
}
|
|
if err := c.ds.Get(ctx, &revision); err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.DetailRevisionResponse{
|
|
ApplicationRevision: revision,
|
|
}, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error) {
|
|
var targetMap = make(map[string]int)
|
|
envbinding, err := c.envBindingUsecase.GetEnvBindings(ctx, app)
|
|
if err != nil {
|
|
log.Logger.Errorf("query app envbinding failure %s", err.Error())
|
|
}
|
|
for _, env := range envbinding {
|
|
for _, target := range env.TargetNames {
|
|
targetMap[target]++
|
|
}
|
|
}
|
|
count, err := c.ds.Count(ctx, &model.ApplicationRevision{AppPrimaryKey: app.PrimaryKey()}, &datastore.FilterOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.ApplicationStatisticsResponse{
|
|
EnvCount: int64(len(envbinding)),
|
|
TargetCount: int64(len(targetMap)),
|
|
RevisionCount: count,
|
|
WorkflowCount: c.workflowUsecase.CountWorkflow(ctx, app),
|
|
}, nil
|
|
}
|
|
|
|
// CompareAppWithLatestRevision compare application with last revision
|
|
func (c *applicationUsecaseImpl) CompareAppWithLatestRevision(ctx context.Context, appModel *model.Application, compareReq apisv1.AppCompareReq) (*apisv1.AppCompareResponse, error) {
|
|
var reqWorkflowName string
|
|
if compareReq.Env != "" {
|
|
reqWorkflowName = convertWorkflowName(compareReq.Env)
|
|
}
|
|
newApp, err := c.renderOAMApplication(ctx, appModel, reqWorkflowName, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ignoreSomeParams(newApp)
|
|
newAppBytes, err := yaml.Marshal(newApp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldApp, err := c.getAppFromLatestRevision(ctx, appModel.Name, compareReq.Env, "")
|
|
if err != nil {
|
|
if errors.Is(err, bcode.ErrApplicationRevisionNotExist) {
|
|
return &apisv1.AppCompareResponse{IsDiff: false, NewAppYAML: string(newAppBytes)}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
ignoreSomeParams(oldApp)
|
|
oldAppBytes, err := yaml.Marshal(oldApp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diffResult, buff, err := compare(ctx, newApp, oldApp)
|
|
if err != nil {
|
|
return &apisv1.AppCompareResponse{IsDiff: false, NewAppYAML: string(newAppBytes), OldAppYAML: string(oldAppBytes)}, err
|
|
}
|
|
return &apisv1.AppCompareResponse{IsDiff: diffResult.DiffType != "", DiffReport: buff.String(), NewAppYAML: string(newAppBytes), OldAppYAML: string(oldAppBytes)}, nil
|
|
}
|
|
|
|
// ResetAppToLatestRevision reset app's component to last revision
|
|
func (c *applicationUsecaseImpl) ResetAppToLatestRevision(ctx context.Context, appName string) (*apisv1.AppResetResponse, error) {
|
|
targetApp, err := c.getAppFromLatestRevision(ctx, appName, "", "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.resetApp(ctx, targetApp)
|
|
}
|
|
|
|
// DryRunAppOrRevision dry-run application or revision
|
|
func (c *applicationUsecaseImpl) DryRunAppOrRevision(ctx context.Context, appModel *model.Application, dryRunReq apisv1.AppDryRunReq) (*apisv1.AppDryRunResponse, error) {
|
|
var app *v1beta1.Application
|
|
var err error
|
|
if dryRunReq.DryRunType == "APP" {
|
|
var reqWorkflowName string
|
|
if dryRunReq.Env != "" {
|
|
reqWorkflowName = convertWorkflowName(dryRunReq.Env)
|
|
}
|
|
app, err = c.renderOAMApplication(ctx, appModel, reqWorkflowName, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
app, err = c.getAppFromLatestRevision(ctx, dryRunReq.AppName, dryRunReq.Env, dryRunReq.Version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
dryRunResult, err := dryRunApplication(ctx, app)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &apisv1.AppDryRunResponse{YAML: dryRunResult.String()}, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) createTargetClusterEnv(ctx context.Context, envBind *model.EnvBinding, env *model.Env, target *model.Target, components []*model.ApplicationComponent) v1alpha1.EnvConfig {
|
|
placement := v1alpha1.EnvPlacement{}
|
|
if target.Cluster != nil {
|
|
placement.ClusterSelector = &common.ClusterSelector{Name: target.Cluster.ClusterName}
|
|
placement.NamespaceSelector = &v1alpha1.NamespaceSelector{Name: target.Cluster.Namespace}
|
|
}
|
|
var componentPatchs []v1alpha1.EnvComponentPatch
|
|
// init cloud application region and provider info
|
|
for _, component := range components {
|
|
definition, err := GetComponentDefinition(ctx, c.kubeClient, component.Type)
|
|
if err != nil {
|
|
log.Logger.Errorf("get component definition %s failure %s", component.Type, err.Error())
|
|
continue
|
|
}
|
|
if definition != nil {
|
|
if definition.Spec.Workload.Type == TerraformWorkloadType ||
|
|
definition.Spec.Workload.Definition.Kind == TerraformWorkloadKind {
|
|
properties := model.JSONStruct{
|
|
"providerRef": map[string]interface{}{
|
|
"name": "default",
|
|
},
|
|
"writeConnectionSecretToRef": map[string]interface{}{
|
|
"name": fmt.Sprintf("%s-%s", component.Name, envBind.Name),
|
|
"namespace": env.Namespace,
|
|
},
|
|
}
|
|
if region, ok := target.Variable["region"]; ok {
|
|
properties["region"] = region
|
|
}
|
|
if providerName, ok := target.Variable["providerName"]; ok {
|
|
properties["providerRef"].(map[string]interface{})["name"] = providerName
|
|
}
|
|
if providerNamespace, ok := target.Variable["providerNamespace"]; ok {
|
|
properties["providerRef"].(map[string]interface{})["namespace"] = providerNamespace
|
|
}
|
|
componentPatchs = append(componentPatchs, v1alpha1.EnvComponentPatch{
|
|
Name: component.Name,
|
|
Properties: properties.RawExtension(),
|
|
Type: component.Type,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return v1alpha1.EnvConfig{
|
|
Name: genPolicyEnvName(target.Name),
|
|
Placement: placement,
|
|
Patch: v1alpha1.EnvPatch{
|
|
Components: componentPatchs,
|
|
},
|
|
}
|
|
}
|
|
|
|
func genPolicyName(envName string) string {
|
|
return fmt.Sprintf("%s-%s", EnvBindingPolicyDefaultName, envName)
|
|
}
|
|
|
|
func genPolicyEnvName(targetName string) string {
|
|
return targetName
|
|
}
|
|
|
|
func genWebhookToken() string {
|
|
rand.Seed(time.Now().UnixNano())
|
|
runes := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
|
|
|
|
b := make([]rune, defaultTokenLen)
|
|
for i := range b {
|
|
b[i] = runes[rand.Intn(len(runes))] // #nosec
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) getAppFromLatestRevision(ctx context.Context, appName string, envName string, version string) (*v1beta1.Application, error) {
|
|
|
|
ar := &model.ApplicationRevision{AppPrimaryKey: appName}
|
|
if envName != "" {
|
|
ar.EnvName = envName
|
|
}
|
|
if version != "" {
|
|
ar.Version = version
|
|
}
|
|
revisions, err := c.ds.List(ctx, ar, &datastore.ListOptions{
|
|
Page: 1,
|
|
PageSize: 1,
|
|
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
|
|
})
|
|
if err != nil || len(revisions) == 0 {
|
|
return nil, bcode.ErrApplicationRevisionNotExist
|
|
}
|
|
latestRevisionRaw := revisions[0]
|
|
latestRevision, ok := latestRevisionRaw.(*model.ApplicationRevision)
|
|
if !ok {
|
|
return nil, errors.New("convert application revision error")
|
|
}
|
|
oldApp := &v1beta1.Application{}
|
|
if err := yaml.Unmarshal([]byte(latestRevision.ApplyAppConfig), oldApp); err != nil {
|
|
return nil, err
|
|
}
|
|
return oldApp, nil
|
|
}
|
|
|
|
func (c *applicationUsecaseImpl) resetApp(ctx context.Context, targetApp *v1beta1.Application) (*apisv1.AppResetResponse, error) {
|
|
appPrimaryKey := targetApp.Name
|
|
|
|
originComps, err := c.ds.List(ctx, &model.ApplicationComponent{AppPrimaryKey: appPrimaryKey}, &datastore.ListOptions{})
|
|
if err != nil {
|
|
return nil, bcode.ErrApplicationComponentNotExist
|
|
}
|
|
|
|
var originCompNames []string
|
|
for _, entity := range originComps {
|
|
comp := entity.(*model.ApplicationComponent)
|
|
originCompNames = append(originCompNames, comp.Name)
|
|
}
|
|
|
|
var targetCompNames []string
|
|
targetComps := targetApp.Spec.Components
|
|
for _, comp := range targetComps {
|
|
targetCompNames = append(targetCompNames, comp.Name)
|
|
}
|
|
|
|
readyToUpdate, readyToDelete, readyToAdd := utils2.ThreeWaySliceCompare(originCompNames, targetCompNames)
|
|
|
|
// delete new app's components
|
|
for _, compName := range readyToDelete {
|
|
var component = model.ApplicationComponent{
|
|
AppPrimaryKey: appPrimaryKey,
|
|
Name: compName,
|
|
}
|
|
if err := c.ds.Delete(ctx, &component); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordNotExist) {
|
|
continue
|
|
}
|
|
log.Logger.Warnf("delete app %s comp %s failure %s", appPrimaryKey, compName, err.Error())
|
|
}
|
|
}
|
|
|
|
for _, comp := range targetComps {
|
|
// add or update new app's components from old app
|
|
if utils.StringsContain(readyToAdd, comp.Name) || utils.StringsContain(readyToUpdate, comp.Name) {
|
|
compModel, err := syncconvert.FromCRComponent(appPrimaryKey, comp)
|
|
if err != nil {
|
|
return &apisv1.AppResetResponse{}, bcode.ErrInvalidProperties
|
|
}
|
|
properties, err := model.NewJSONStruct(comp.Properties)
|
|
if err != nil {
|
|
return &apisv1.AppResetResponse{}, bcode.ErrInvalidProperties
|
|
}
|
|
compModel.Properties = properties
|
|
if err := c.ds.Add(ctx, &compModel); err != nil {
|
|
if errors.Is(err, datastore.ErrRecordExist) {
|
|
err := c.ds.Put(ctx, &compModel)
|
|
if err != nil {
|
|
log.Logger.Warnf("update comp %s for app %s failure %s", comp.Name, utils2.Sanitize(appPrimaryKey), err.Error())
|
|
}
|
|
return &apisv1.AppResetResponse{IsReset: true}, err
|
|
}
|
|
log.Logger.Warnf("add comp %s for app %s failure %s", comp.Name, utils2.Sanitize(appPrimaryKey), err.Error())
|
|
return &apisv1.AppResetResponse{}, err
|
|
}
|
|
}
|
|
}
|
|
return &apisv1.AppResetResponse{IsReset: true}, nil
|
|
}
|
|
|
|
func dryRunApplication(ctx context.Context, app *v1beta1.Application) (bytes.Buffer, error) {
|
|
c := common2.Args{
|
|
Schema: common2.Scheme,
|
|
}
|
|
var buff = bytes.Buffer{}
|
|
newClient, err := c.GetClient()
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
var objs []oam.Object
|
|
pd, err := c.GetPackageDiscover()
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
dm, err := discoverymapper.New(config)
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
dryRunOpt := dryrun.NewDryRunOption(newClient, config, dm, pd, objs)
|
|
comps, err := dryRunOpt.ExecuteDryRun(ctx, app)
|
|
if err != nil {
|
|
return buff, errors.New("generate OAM objects")
|
|
}
|
|
var components = make(map[string]*unstructured.Unstructured)
|
|
for _, comp := range comps {
|
|
components[comp.Name] = comp.StandardWorkload
|
|
}
|
|
buff.Write([]byte(fmt.Sprintf("---\n# Application(%s) \n---\n\n", app.Name)))
|
|
result, err := yaml.Marshal(app)
|
|
if err != nil {
|
|
return buff, errors.New("marshal app error")
|
|
}
|
|
buff.Write(result)
|
|
buff.Write([]byte("\n---\n"))
|
|
for _, c := range comps {
|
|
buff.Write([]byte(fmt.Sprintf("---\n# Application(%s) -- Component(%s) \n---\n\n", app.Name, c.Name)))
|
|
result, err := yaml.Marshal(components[c.Name])
|
|
if err != nil {
|
|
return buff, errors.New("marshal result for component " + c.Name + " object in yaml format")
|
|
}
|
|
buff.Write(result)
|
|
buff.Write([]byte("\n---\n"))
|
|
for _, t := range c.Traits {
|
|
result, err := yaml.Marshal(t)
|
|
if err != nil {
|
|
return buff, errors.New("marshal result for component " + c.Name + " object in yaml format")
|
|
}
|
|
buff.Write(result)
|
|
buff.Write([]byte("\n---\n"))
|
|
}
|
|
buff.Write([]byte("\n"))
|
|
}
|
|
return buff, nil
|
|
}
|
|
|
|
func ignoreSomeParams(o *v1beta1.Application) {
|
|
// set default
|
|
o.ResourceVersion = ""
|
|
o.Spec.Workflow = nil
|
|
newAnnotations := map[string]string{}
|
|
annotations := o.GetAnnotations()
|
|
for k, v := range annotations {
|
|
if k == oam.AnnotationDeployVersion || k == oam.AnnotationPublishVersion || k == "kubectl.kubernetes.io/last-applied-configuration" {
|
|
continue
|
|
}
|
|
newAnnotations[k] = v
|
|
}
|
|
o.SetAnnotations(newAnnotations)
|
|
}
|
|
|
|
func compare(ctx context.Context, newApp *v1beta1.Application, oldApp *v1beta1.Application) (*dryrun.DiffEntry, bytes.Buffer, error) {
|
|
var buff = bytes.Buffer{}
|
|
c := common2.Args{
|
|
Schema: common2.Scheme,
|
|
}
|
|
_, err := c.GetClient()
|
|
if err != nil {
|
|
return nil, buff, err
|
|
}
|
|
pd, err := c.GetPackageDiscover()
|
|
if err != nil {
|
|
return nil, buff, err
|
|
}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return nil, buff, err
|
|
}
|
|
dm, err := discoverymapper.New(config)
|
|
if err != nil {
|
|
return nil, buff, err
|
|
}
|
|
var objs []oam.Object
|
|
client, err := c.GetClient()
|
|
if err != nil {
|
|
return nil, buff, err
|
|
}
|
|
liveDiffOption := dryrun.NewLiveDiffOption(client, config, dm, pd, objs)
|
|
diffResult, err := liveDiffOption.DiffApps(ctx, newApp, oldApp)
|
|
if err != nil {
|
|
return nil, buff, err
|
|
}
|
|
reportDiffOpt := dryrun.NewReportDiffOption(10, &buff)
|
|
reportDiffOpt.PrintDiffReport(diffResult)
|
|
return diffResult, buff, nil
|
|
}
|