Feat: refactor envbinding for adapt policy placement (#2731)

* Feat: refactor envbinding for adapt policy placement

* Fix: refactor envbinding logic

* Fix: fix some bug

* Fix: fix unit test

* Fix: fix unit test

* Fix: fix unit test

* Fix: fix unit test

* Fix: fix unit test ...

* Fix: fix unit test

Co-authored-by: zhuxiaobing <zhuxiaobing@lixiang.com>
This commit is contained in:
朱晓兵
2021-11-19 10:37:40 +08:00
committed by GitHub
co-authored by zhuxiaobing
parent b6a14e435b
commit ffd25a4cbf
15 changed files with 751 additions and 550 deletions
-11
View File
@@ -36,7 +36,6 @@ type Application struct {
Description string `json:"description"`
Icon string `json:"icon"`
Labels map[string]string `json:"labels,omitempty"`
EnvBinding []*EnvBinding `json:"envBinding,omitempty"`
}
// TableName return custom table name
@@ -61,16 +60,6 @@ func (a *Application) Index() map[string]string {
return index
}
// EnvBinding application env binding
type EnvBinding struct {
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description,omitempty"`
TargetNames []string `json:"targetNames"`
ComponentSelector *ComponentSelector `json:"componentSelector"`
//TODO: componentPatchs
}
// ClusterSelector cluster selector
type ClusterSelector struct {
Name string `json:"name"`
+57
View File
@@ -0,0 +1,57 @@
/*
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 model
import "fmt"
func init() {
RegistModel(&EnvBinding{})
}
// EnvBinding application env binding
type EnvBinding struct {
Model
AppPrimaryKey string `json:"appPrimaryKey"`
Name string `json:"name"`
Alias string `json:"alias"`
Description string `json:"description,omitempty"`
TargetNames []string `json:"targetNames"`
ComponentSelector *ComponentSelector `json:"componentSelector"`
//TODO: componentPatchs
}
// TableName return custom table name
func (e *EnvBinding) TableName() string {
return tableNamePrefix + "envbinding"
}
// PrimaryKey return custom primary key
func (e *EnvBinding) PrimaryKey() string {
return fmt.Sprintf("%s-%s", e.AppPrimaryKey, e.Name)
}
// Index return custom index
func (e *EnvBinding) Index() map[string]string {
index := make(map[string]string)
if e.Name != "" {
index["name"] = e.Name
}
if e.AppPrimaryKey != "" {
index["appPrimaryKey"] = e.AppPrimaryKey
}
return index
}
+27 -4
View File
@@ -253,7 +253,6 @@ type ApplicationBase struct {
UpdateTime time.Time `json:"updateTime"`
Icon string `json:"icon"`
Labels map[string]string `json:"labels,omitempty"`
EnvBinding EnvBindingList `json:"envBinding,omitempty"`
}
// ApplicationStatusResponse application status response body
@@ -292,6 +291,21 @@ type EnvBinding struct {
ComponentSelector *ComponentSelector `json:"componentSelector" optional:"true"`
}
// EnvBindingBase application env binding
type EnvBindingBase struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
TargetNames []string `json:"targetNames"`
ComponentSelector *ComponentSelector `json:"componentSelector" optional:"true"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
type DetailEnvBindingResponse struct {
EnvBindingBase
}
// ClusterSelector cluster selector
type ClusterSelector struct {
Name string `json:"name" validate:"checkname"`
@@ -308,6 +322,7 @@ type ComponentSelector struct {
type DetailApplicationResponse struct {
ApplicationBase
Policies []string `json:"policies"`
EnvBindings []string `json:"envBindings"`
Status string `json:"status"`
ResourceInfo ApplicationResourceInfo `json:"resourceInfo"`
WorkflowStatus []WorkflowStepStatus `json:"workflowStatus"`
@@ -500,6 +515,7 @@ type CreateWorkflowRequest struct {
Description string `json:"description" optional:"true"`
Steps []WorkflowStep `json:"steps,omitempty"`
Default bool `json:"default"`
EnvName string `json:"envName"`
}
// UpdateWorkflowRequest update or create application workflow
@@ -509,6 +525,7 @@ type UpdateWorkflowRequest struct {
Steps []WorkflowStep `json:"steps,omitempty"`
Enable bool `json:"enable"`
Default bool `json:"default"`
EnvName string `json:"envName"`
}
// WorkflowStep workflow step config
@@ -543,6 +560,7 @@ type WorkflowBase struct {
Description string `json:"description"`
Enable bool `json:"enable"`
Default bool `json:"default"`
EnvName string `json:"envName"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
@@ -595,9 +613,14 @@ type VelaQLViewResponse map[string]interface{}
// PutApplicationEnvRequest set diff request
type PutApplicationEnvRequest struct {
ComponentSelector *ComponentSelector `json:"componentSelector,omitempty"`
Alias *string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Description *string `json:"description,omitempty" optional:"true"`
TargetNames *[]string `json:"targetNames"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Description string `json:"description,omitempty" optional:"true"`
TargetNames []string `json:"targetNames"`
}
// ListApplicationEnvBinding list app envBindings
type ListApplicationEnvBinding struct {
EnvBindings []*EnvBindingBase `json:"envBindings"`
}
// CreateApplicationEnvRequest new application env
+133 -286
View File
@@ -77,10 +77,6 @@ type ApplicationUsecase interface {
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)
GetApplicationEnvBindingPolicy(ctx context.Context, app *model.Application) (*v1alpha1.EnvBindingSpec, error)
UpdateApplicationEnvBinding(ctx context.Context, app *model.Application, envName string, diff apisv1.PutApplicationEnvRequest) (*apisv1.EnvBinding, error)
CreateApplicationEnvBinding(ctx context.Context, app *model.Application, env apisv1.CreateApplicationEnvRequest) (*apisv1.EnvBinding, error)
DeleteApplicationEnvBinding(ctx context.Context, app *model.Application, envName string) 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)
@@ -89,23 +85,27 @@ type ApplicationUsecase interface {
}
type applicationUsecaseImpl struct {
ds datastore.DataStore
kubeClient client.Client
apply apply.Applicator
workflowUsecase WorkflowUsecase
ds datastore.DataStore
kubeClient client.Client
apply apply.Applicator
workflowUsecase WorkflowUsecase
envBindingUsecase EnvBindingUsecase
deliveryTargetUsecase DeliveryTargetUsecase
}
// NewApplicationUsecase new application usecase
func NewApplicationUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase) ApplicationUsecase {
func NewApplicationUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase, envBindingUsecase EnvBindingUsecase, deliveryTargetUsecase DeliveryTargetUsecase) ApplicationUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
return &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
kubeClient: kubecli,
apply: apply.NewAPIApplicator(kubecli),
ds: ds,
workflowUsecase: workflowUsecase,
envBindingUsecase: envBindingUsecase,
deliveryTargetUsecase: deliveryTargetUsecase,
kubeClient: kubecli,
apply: apply.NewAPIApplicator(kubecli),
}
}
@@ -128,8 +128,11 @@ func (c *applicationUsecaseImpl) ListApplications(ctx context.Context, listOptio
strings.Contains(appBase.Description, listOptions.Query)) {
continue
}
if listOptions.TargetName != "" && !appBase.EnvBinding.ContainTarget(listOptions.TargetName) {
continue
if listOptions.TargetName != "" {
targetIsContain, _ := c.envBindingUsecase.CheckAppEnvBindingsContainTarget(ctx, &app, listOptions.TargetName)
if targetIsContain {
continue
}
}
list = append(list, appBase)
}
@@ -161,16 +164,22 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod
return nil, err
}
components, err := c.ListComponents(ctx, app, apisv1.ListApplicationComponentOptions{})
envBindings, err := c.envBindingUsecase.GetEnvBindings(ctx, app)
if err != nil {
return nil, err
}
var policyNames []string
var envBindingNames []string
for _, p := range policys {
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: len(components),
},
@@ -236,45 +245,22 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
}
}
if oamApp.Spec.Workflow != nil && len(oamApp.Spec.Workflow.Steps) > 0 {
var steps []apisv1.WorkflowStep
for _, step := range oamApp.Spec.Workflow.Steps {
var propertyStr string
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", application.Name, step.Name, err.Error())
continue
}
propertyStr = properties.JSON()
}
steps = append(steps, apisv1.WorkflowStep{
Name: step.Name,
Type: step.Type,
DependsOn: step.DependsOn,
Properties: propertyStr,
Inputs: step.Inputs,
Outputs: step.Outputs,
})
}
_, err := c.workflowUsecase.CreateWorkflow(ctx, &application, apisv1.CreateWorkflowRequest{
AppName: application.PrimaryKey(),
Name: application.Name,
Description: "Created automatically.",
Steps: steps,
Default: true,
})
if err != nil {
if err := c.saveApplicationWorkflow(ctx, &application, oamApp.Spec.Workflow.Steps, application.Name); err != nil {
log.Logger.Errorf("save applictaion polocies failure,%s", err.Error())
return nil, err
}
}
// TODO Waiting for Spec.EnvBinding support
}
// build-in create env binding policy
// build-in create env binding
if len(req.EnvBinding) > 0 {
if _, err := c.createApplictionEnvBindingPolicy(ctx, &application, req.EnvBinding); err != nil {
err := c.saveApplicationEnvBinding(ctx, application, req.EnvBinding)
if err != nil {
return nil, err
}
}
if req.Component != nil {
_, err = c.AddComponent(ctx, &application, *req.Component)
if err != nil {
@@ -293,6 +279,70 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
return base, nil
}
func (c *applicationUsecaseImpl) genPolicyByEnv(ctx context.Context, app *model.Application, envName string) (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)
var envBindingSpec v1alpha1.EnvBindingSpec
for _, targetName := range envBinding.TargetNames {
target, err := c.deliveryTargetUsecase.GetDeliveryTarget(ctx, targetName)
if err != nil || target == nil {
return appPolicy, bcode.ErrFoundEnvbindingDeliveryTarget
}
envBindingSpec.Envs = append(envBindingSpec.Envs, createTargetClusterEnv(envBinding.EnvBindingBase, target))
}
properties, err := model.NewJSONStructByStruct(envBindingSpec)
appPolicy.Properties = properties.RawExtension()
return appPolicy, nil
}
func (c *applicationUsecaseImpl) saveApplicationWorkflow(ctx context.Context, application *model.Application, workflowSteps []v1beta1.WorkflowStep, workflowName string) error {
var steps []apisv1.WorkflowStep
for _, step := range workflowSteps {
var propertyStr string
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", application.Name, step.Name, err.Error())
continue
}
propertyStr = properties.JSON()
}
steps = append(steps, apisv1.WorkflowStep{
Name: step.Name,
Type: step.Type,
DependsOn: step.DependsOn,
Properties: propertyStr,
Inputs: step.Inputs,
Outputs: step.Outputs,
})
}
_, err := c.workflowUsecase.CreateWorkflow(ctx, application, apisv1.CreateWorkflowRequest{
AppName: application.PrimaryKey(),
Name: workflowName,
Description: "Created automatically.",
Steps: steps,
Default: true,
})
if err != nil {
return err
}
return 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) {
app.Alias = req.Alias
app.Description = req.Description
@@ -354,15 +404,15 @@ func (c *applicationUsecaseImpl) ListComponents(ctx context.Context, app *model.
envComponents := map[string]bool{}
componentSelectorDefine := false
if op.EnvName != "" {
envbinding, err := c.GetApplicationEnvBindingPolicy(ctx, app)
envbindings, err := c.envBindingUsecase.GetEnvBindings(ctx, app)
if err != nil && !errors.Is(err, bcode.ErrApplicationNotEnv) {
log.Logger.Errorf("query app env binding policy config failure %s", err.Error())
}
if envbinding != nil {
for _, env := range envbinding.Envs {
if env.Selector != nil && env.Name == op.EnvName {
if len(envbindings) > 0 {
for _, env := range envbindings {
if env != nil && env.Name == op.EnvName {
componentSelectorDefine = true
for _, componentName := range env.Selector.Components {
for _, componentName := range env.ComponentSelector.Components {
envComponents[componentName] = true
}
}
@@ -474,7 +524,6 @@ func (c *applicationUsecaseImpl) saveApplicationPolicy(ctx context.Context, app
if env.Selector != nil {
envBind.ComponentSelector = (*model.ComponentSelector)(env.Selector)
}
app.EnvBinding = append(app.EnvBinding, envBind)
}
}
return c.ds.BatchAdd(ctx, policyModels)
@@ -495,54 +544,6 @@ func (c *applicationUsecaseImpl) queryApplicationPolicys(ctx context.Context, ap
return
}
func (c *applicationUsecaseImpl) GetApplicationEnvBindingPolicy(ctx context.Context, app *model.Application) (*v1alpha1.EnvBindingSpec, error) {
var policy = model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
Type: string(EnvBindingPolicy),
Name: EnvBindingPolicyDefaultName,
}
err := c.ds.Get(ctx, &policy)
if err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrApplicationNotEnv
}
return nil, err
}
var envBindingSpec v1alpha1.EnvBindingSpec
if err := json.Unmarshal([]byte(policy.Properties.JSON()), &envBindingSpec); err != nil {
return nil, err
}
return &envBindingSpec, nil
}
// nolint
func (c *applicationUsecaseImpl) createApplictionEnvBindingPolicy(ctx context.Context, app *model.Application, envbinds apisv1.EnvBindingList) (*model.ApplicationPolicy, error) {
policy := &model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
Name: EnvBindingPolicyDefaultName,
Description: "build-in create",
Type: string(EnvBindingPolicy),
Creator: "",
}
var envBindingSpec v1alpha1.EnvBindingSpec
for _, envBind := range envbinds {
// TODO: check delivery target
envBindingSpec.Envs = append(envBindingSpec.Envs, createEnvBind(*envBind))
app.EnvBinding = append(app.EnvBinding, createModelEnvBind(*envBind))
}
properties, err := model.NewJSONStructByStruct(envBindingSpec)
if err != nil {
log.Logger.Errorf("new env binding properties failure,%s", err.Error())
return nil, bcode.ErrInvalidProperties
}
policy.Properties = properties
if err := c.ds.Add(ctx, policy); err != nil {
log.Logger.Errorf("save env binding policy failure,%s", err.Error())
return nil, err
}
return policy, nil
}
// 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) {
@@ -727,6 +728,14 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
if err != nil {
return nil, err
}
if workflow.EnvName != "" {
envPolicy, err := c.genPolicyByEnv(ctx, appModel, workflow.EnvName)
if err != nil {
return nil, err
}
app.Spec.Policies = append(app.Spec.Policies, envPolicy)
}
} else {
workflow, err = c.workflowUsecase.GetApplicationDefaultWorkflow(ctx, appModel)
if err != nil && !errors.Is(err, bcode.ErrWorkflowNoDefault) {
@@ -747,11 +756,13 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
if step.Properties != nil {
wstep.Properties = step.Properties.RawExtension()
}
steps = append(steps, wstep)
}
app.Spec.Workflow = &v1beta1.Workflow{
Steps: steps,
}
}
return app, nil
}
@@ -766,18 +777,6 @@ func (c *applicationUsecaseImpl) converAppModelToBase(app *model.Application) *a
Icon: app.Icon,
Labels: app.Labels,
}
for _, envBind := range app.EnvBinding {
apiEnvBind := &apisv1.EnvBinding{
Name: envBind.Name,
Alias: envBind.Alias,
Description: envBind.Description,
TargetNames: envBind.TargetNames,
}
if envBind.ComponentSelector != nil {
apiEnvBind.ComponentSelector = (*apisv1.ComponentSelector)(envBind.ComponentSelector)
}
appBase.EnvBinding = append(appBase.EnvBinding, apiEnvBind)
}
return appBase
}
@@ -795,6 +794,7 @@ func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *mod
if err != nil {
return err
}
// delete workflow
if err := c.workflowUsecase.DeleteWorkflow(ctx, app.Name); err != nil && !errors.Is(err, bcode.ErrWorkflowNotExist) {
log.Logger.Errorf("delete workflow %s failure %s", app.Name, err.Error())
@@ -814,6 +814,10 @@ func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *mod
}
}
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)
}
@@ -946,166 +950,6 @@ func (c *applicationUsecaseImpl) UpdatePolicy(ctx context.Context, app *model.Ap
}, nil
}
// UpdateApplicationEnvBinding update application env binding diff
func (c *applicationUsecaseImpl) UpdateApplicationEnvBinding(
ctx context.Context,
app *model.Application,
envName string,
envUpdate apisv1.PutApplicationEnvRequest) (*apisv1.EnvBinding, error) {
// update env-binding policy
envBinding, err := c.GetApplicationEnvBindingPolicy(ctx, app)
if err != nil {
return nil, err
}
for i, env := range envBinding.Envs {
if env.Name == envName {
if envUpdate.ComponentSelector == nil {
envBinding.Envs[i].Selector = nil
} else {
envBinding.Envs[i].Selector = &v1alpha1.EnvSelector{
Components: envUpdate.ComponentSelector.Components,
}
}
}
}
properties, err := model.NewJSONStructByStruct(envBinding)
if err != nil {
log.Logger.Errorf("new env binding properties failure,%s", err.Error())
return nil, bcode.ErrInvalidProperties
}
policy := &model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
Name: EnvBindingPolicyDefaultName,
}
if err := c.ds.Get(ctx, policy); err != nil {
return nil, err
}
policy.Properties = properties
if err := c.ds.Put(ctx, policy); err != nil {
return nil, err
}
var envBind model.EnvBinding
// update env-binding base
for i, env := range app.EnvBinding {
if env.Name == envName {
if envUpdate.Description != nil {
app.EnvBinding[i].Description = *envUpdate.Description
}
if envUpdate.Alias != nil {
app.EnvBinding[i].Alias = *envUpdate.Alias
}
if envUpdate.TargetNames != nil {
app.EnvBinding[i].TargetNames = *envUpdate.TargetNames
}
if envUpdate.ComponentSelector == nil {
app.EnvBinding[i].ComponentSelector = nil
} else {
app.EnvBinding[i].ComponentSelector = &model.ComponentSelector{
Components: envUpdate.ComponentSelector.Components,
}
}
envBind = *app.EnvBinding[i]
}
}
if err := c.ds.Put(ctx, app); err != nil {
return nil, err
}
re := &apisv1.EnvBinding{
Name: envBind.Name,
Alias: envBind.Alias,
Description: envBind.Description,
TargetNames: envBind.TargetNames,
}
if envBind.ComponentSelector != nil {
re.ComponentSelector = (*apisv1.ComponentSelector)(envBind.ComponentSelector)
}
return re, nil
}
// CreateApplicationEnvBinding create application env
func (c *applicationUsecaseImpl) CreateApplicationEnvBinding(ctx context.Context, app *model.Application, envReq apisv1.CreateApplicationEnvRequest) (*apisv1.EnvBinding, error) {
for _, env := range app.EnvBinding {
if env.Name == envReq.Name {
return nil, bcode.ErrApplicationEnvExist
}
}
envBinding, err := c.GetApplicationEnvBindingPolicy(ctx, app)
if err != nil {
if !errors.Is(err, bcode.ErrApplicationNotEnv) {
return nil, err
}
}
if envBinding == nil {
_, err := c.createApplictionEnvBindingPolicy(ctx, app, []*apisv1.EnvBinding{&envReq.EnvBinding})
if err != nil {
return nil, err
}
} else {
app.EnvBinding = append(app.EnvBinding, createModelEnvBind(envReq.EnvBinding))
envBinding.Envs = append(envBinding.Envs, createEnvBind(envReq.EnvBinding))
properties, err := model.NewJSONStructByStruct(envBinding)
if err != nil {
log.Logger.Errorf("new env binding properties failure,%s", err.Error())
return nil, bcode.ErrInvalidProperties
}
policy := &model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
Name: EnvBindingPolicyDefaultName,
}
if err := c.ds.Get(ctx, policy); err != nil {
return nil, err
}
policy.Properties = properties
if err := c.ds.Put(ctx, policy); err != nil {
return nil, err
}
}
if err := c.ds.Put(ctx, app); err != nil {
return nil, err
}
return &envReq.EnvBinding, nil
}
// DeleteApplicationEnvBinding delete application env binding
func (c *applicationUsecaseImpl) DeleteApplicationEnvBinding(ctx context.Context, app *model.Application, envName string) error {
for i, envBind := range app.EnvBinding {
if envBind.Name == envName {
app.EnvBinding = append(app.EnvBinding[0:i], app.EnvBinding[i+1:]...)
}
}
envBinding, err := c.GetApplicationEnvBindingPolicy(ctx, app)
if err != nil {
return err
}
for i, envBind := range envBinding.Envs {
if envBind.Name == envName {
envBinding.Envs = append(envBinding.Envs[0:i], envBinding.Envs[i+1:]...)
}
}
properties, err := model.NewJSONStructByStruct(envBinding)
if err != nil {
log.Logger.Errorf("new env binding properties failure,%s", err.Error())
return bcode.ErrInvalidProperties
}
policy := &model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
Name: EnvBindingPolicyDefaultName,
}
if err := c.ds.Get(ctx, policy); err != nil {
return err
}
policy.Properties = properties
if err := c.ds.Put(ctx, policy); err != nil {
return err
}
if err := c.ds.Put(ctx, app); err != nil {
return err
}
return 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(),
@@ -1235,7 +1079,7 @@ func (c *applicationUsecaseImpl) DetailRevision(ctx context.Context, appName, re
}, nil
}
func createEnvBind(envBind apisv1.EnvBinding) v1alpha1.EnvConfig {
func createTargetClusterEnv(envBind apisv1.EnvBindingBase, target *model.DeliveryTarget) v1alpha1.EnvConfig {
placement := v1alpha1.EnvPlacement{}
var componentSelector *v1alpha1.EnvSelector
if envBind.ComponentSelector != nil {
@@ -1243,26 +1087,29 @@ func createEnvBind(envBind apisv1.EnvBinding) v1alpha1.EnvConfig {
Components: envBind.ComponentSelector.Components,
}
}
if target.Cluster != nil {
placement.ClusterSelector = &common.ClusterSelector{Name: target.Cluster.ClusterName}
placement.NamespaceSelector = &v1alpha1.NamespaceSelector{Name: target.Cluster.Namespace}
}
return v1alpha1.EnvConfig{
Name: envBind.Name,
Name: genPolicyEnvName(target.Name),
Placement: placement,
Selector: componentSelector,
}
}
func createModelEnvBind(envBind apisv1.EnvBinding) *model.EnvBinding {
re := model.EnvBinding{
Name: envBind.Name,
Description: envBind.Description,
Alias: envBind.Alias,
TargetNames: envBind.TargetNames,
}
if envBind.ComponentSelector != nil {
re.ComponentSelector = (*model.ComponentSelector)(envBind.ComponentSelector)
}
return &re
}
func converAppName(app *model.Application, envName string) string {
return fmt.Sprintf("%s-%s", app.Name, envName)
}
func genPolicyName(envName string) string {
return fmt.Sprintf("%s-%s", EnvBindingPolicyDefaultName, envName)
}
func genWorkflowName(app *model.Application, envName string) string {
return fmt.Sprintf("%s-%s", app.Name, envName)
}
func genPolicyEnvName(targetName string) string {
return targetName
}
+16 -106
View File
@@ -37,16 +37,22 @@ import (
var _ = Describe("Test application usecase function", func() {
var (
appUsecase *applicationUsecaseImpl
workflowUsecase *workflowUsecaseImpl
appUsecase *applicationUsecaseImpl
workflowUsecase *workflowUsecaseImpl
envBindingUsecase *envBindingUsecaseImpl
deliveryTargetUsecase *deliveryTargetUsecaseImpl
)
BeforeEach(func() {
workflowUsecase = &workflowUsecaseImpl{ds: ds}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, workflowUsecase: workflowUsecase}
deliveryTargetUsecase = &deliveryTargetUsecaseImpl{ds: ds}
appUsecase = &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
apply: apply.NewAPIApplicator(k8sClient),
kubeClient: k8sClient,
ds: ds,
workflowUsecase: workflowUsecase,
apply: apply.NewAPIApplicator(k8sClient),
kubeClient: k8sClient,
envBindingUsecase: envBindingUsecase,
deliveryTargetUsecase: deliveryTargetUsecase,
}
})
It("Test CreateApplication function", func() {
@@ -138,7 +144,7 @@ var _ = Describe("Test application usecase function", func() {
}
appBase, err := appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(appBase.EnvBinding), 2)).Should(BeEmpty())
Expect(cmp.Diff(appBase.Name, "test-app-sadasd4")).Should(BeEmpty())
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd4")
Expect(err).Should(BeNil())
@@ -146,23 +152,6 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test GetApplicationEnvBindingingPolicy", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd4")
Expect(err).Should(BeNil())
envBinding, err := appUsecase.GetApplicationEnvBindingPolicy(context.TODO(), appModel)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(envBinding.Envs), 2)).Should(BeEmpty())
})
It("Test UpdateApplicationEnvBindingingDiff", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
_, err = appUsecase.UpdateApplicationEnvBinding(context.TODO(), appModel, "staging", v1.PutApplicationEnvRequest{
ComponentSelector: &v1.ComponentSelector{Components: []string{"hello-world-server"}},
})
Expect(err).Should(BeNil())
})
It("Test ListApplications function", func() {
apps, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicatioOptions{})
Expect(err).Should(BeNil())
@@ -216,15 +205,15 @@ var _ = Describe("Test application usecase function", func() {
EnvName: "test",
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 1)).Should(BeEmpty())
Expect(cmp.Diff(len(components), 2)).Should(BeEmpty())
Expect(cmp.Diff(components[0].Name, "data-worker")).Should(BeEmpty())
components, err = appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{
EnvName: "staging",
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 1)).Should(BeEmpty())
Expect(cmp.Diff(components[0].Name, "hello-world-server")).Should(BeEmpty())
Expect(cmp.Diff(len(components), 2)).Should(BeEmpty())
Expect(cmp.Diff(components[0].Name, "data-worker")).Should(BeEmpty())
})
It("Test DetailComponent function", func() {
@@ -408,85 +397,6 @@ var _ = Describe("Test application usecase function", func() {
Expect(err).Should(BeNil())
})
It("Test CreateApplicationEnvBinding function", func() {
req := v1.CreateApplicationRequest{
Name: "not-have-env-bind",
Namespace: "test-app-namespace",
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
}
_, err := appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
appModel4, err := appUsecase.GetApplication(context.TODO(), "not-have-env-bind")
Expect(err).Should(BeNil())
By("test create first env")
env4, err := appUsecase.CreateApplicationEnvBinding(context.TODO(), appModel4, v1.CreateApplicationEnvRequest{
EnvBinding: v1.EnvBinding{
Name: "prod2",
Alias: "生产环境",
Description: "这是一个用户某客户的生产环境",
TargetNames: []string{"prod-target"},
},
})
Expect(err).Should(BeNil())
Expect(env4).ShouldNot(BeNil())
appModelNew, err := appUsecase.GetApplication(context.TODO(), "not-have-env-bind")
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(appModelNew.EnvBinding), 1)).Should(BeEmpty())
By("test create not first env")
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Namespace, "test-app-namespace")).Should(BeEmpty())
env, err := appUsecase.CreateApplicationEnvBinding(context.TODO(), appModel, v1.CreateApplicationEnvRequest{
EnvBinding: v1.EnvBinding{
Name: "prod2",
Alias: "生产环境",
Description: "这是一个用户某客户的生产环境",
TargetNames: []string{"prod-target"},
},
})
Expect(err).Should(BeNil())
Expect(env).ShouldNot(BeNil())
appModelNew, err = appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(appModelNew.EnvBinding), 4)).Should(BeEmpty())
spec, err := appUsecase.GetApplicationEnvBindingPolicy(context.TODO(), appModelNew)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(spec.Envs), 4)).Should(BeEmpty())
})
It("Test UpdateApplicationEnvBinding function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Namespace, "test-app-namespace")).Should(BeEmpty())
env, err := appUsecase.UpdateApplicationEnvBinding(context.TODO(), appModel, "prod2", v1.PutApplicationEnvRequest{
ComponentSelector: &v1.ComponentSelector{
Components: []string{},
},
})
Expect(err).Should(BeNil())
Expect(env).ShouldNot(BeNil())
components, err := appUsecase.ListComponents(context.TODO(), appModel, v1.ListApplicationComponentOptions{
EnvName: "prod2",
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(components), 0)).Should(BeEmpty())
})
It("Test DeleteApplicationEnvBindinging function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
Expect(cmp.Diff(appModel.Namespace, "test-app-namespace")).Should(BeEmpty())
err = appUsecase.DeleteApplicationEnvBinding(context.TODO(), appModel, "prod2")
Expect(err).Should(BeNil())
})
It("Test Deploy Application function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd")
Expect(err).Should(BeNil())
@@ -37,15 +37,17 @@ type DeliveryTargetUsecase interface {
ListDeliveryTargets(ctx context.Context, page, pageSize int, namespace string) (*apisv1.ListDeliveryTargetResponse, error)
}
// NewDeliveryTargetUsecase new DeliveryTarget usecase
func NewDeliveryTargetUsecase(ds datastore.DataStore) DeliveryTargetUsecase {
return &deliveryTargetUsecaseImpl{ds: ds}
}
type deliveryTargetUsecaseImpl struct {
ds datastore.DataStore
}
// NewDeliveryTargetUsecase new DeliveryTarget usecase
func NewDeliveryTargetUsecase(ds datastore.DataStore) DeliveryTargetUsecase {
return &deliveryTargetUsecaseImpl{
ds: ds,
}
}
func (dt *deliveryTargetUsecaseImpl) ListDeliveryTargets(ctx context.Context, page, pageSize int, namespace string) (*apisv1.ListDeliveryTargetResponse, error) {
deliveryTarget := model.DeliveryTarget{}
if namespace != "" {
+288
View File
@@ -0,0 +1,288 @@
/*
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 (
"context"
"errors"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
)
// EnvBindingUsecase envbinding usecase
type EnvBindingUsecase interface {
GetEnvBindings(ctx context.Context, app *model.Application) ([]*apisv1.EnvBindingBase, error)
GetEnvBinding(ctx context.Context, app *model.Application, envName string) (*apisv1.DetailEnvBindingResponse, error)
CheckAppEnvBindingsContainTarget(ctx context.Context, app *model.Application, targetName string) (bool, error)
CreateEnvBinding(ctx context.Context, app *model.Application, env apisv1.CreateApplicationEnvRequest) (*apisv1.EnvBinding, error)
BatchCreateEnvBinding(ctx context.Context, app *model.Application, env apisv1.EnvBindingList) error
UpdateEnvBinding(ctx context.Context, app *model.Application, envName string, diff apisv1.PutApplicationEnvRequest) (*apisv1.DetailEnvBindingResponse, error)
DeleteEnvBinding(ctx context.Context, app *model.Application, envName string) error
BatchDeleteEnvBinding(ctx context.Context, app *model.Application) error
DetailEnvBinding(ctx context.Context, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error)
}
type envBindingUsecaseImpl struct {
ds datastore.DataStore
workflowUsecase WorkflowUsecase
}
// NewEnvBindingUsecase new envBinding usecase
func NewEnvBindingUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase) EnvBindingUsecase {
return &envBindingUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
}
}
func (e *envBindingUsecaseImpl) GetEnvBindings(ctx context.Context, app *model.Application) ([]*apisv1.EnvBindingBase, error) {
var envBinding = model.EnvBinding{
AppPrimaryKey: app.PrimaryKey(),
}
envBindings, err := e.ds.List(ctx, &envBinding, &datastore.ListOptions{})
if err != nil {
return nil, bcode.ErrEnvBindingsNotExist
}
var list []*apisv1.EnvBindingBase
for _, ebd := range envBindings {
eb := ebd.(*model.EnvBinding)
list = append(list, convertEnvbindingModelToBase(eb))
}
return list, nil
}
func (e *envBindingUsecaseImpl) GetEnvBinding(ctx context.Context, app *model.Application, envName string) (*apisv1.DetailEnvBindingResponse, error) {
envBinding, err := e.getBindingByEnv(ctx, app, envName)
if err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrEnvBindingsNotExist
}
return nil, err
}
return e.DetailEnvBinding(ctx, envBinding)
}
func (e *envBindingUsecaseImpl) CheckAppEnvBindingsContainTarget(ctx context.Context, app *model.Application, targetName string) (bool, error) {
envBindings, err := e.GetEnvBindings(ctx, app)
if err != nil {
return false, err
}
var filteredList []*apisv1.EnvBindingBase
for _, envBinding := range envBindings {
if utils.StringsContain(envBinding.TargetNames, targetName) {
filteredList = append(filteredList, envBinding)
}
}
return len(filteredList) > 0, nil
}
func (e *envBindingUsecaseImpl) CreateEnvBinding(ctx context.Context, app *model.Application, envReq apisv1.CreateApplicationEnvRequest) (*apisv1.EnvBinding, error) {
envBinding, err := e.getBindingByEnv(ctx, app, envReq.Name)
if err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
return nil, err
}
}
if envBinding != nil {
return nil, bcode.ErrEnvBindingExist
} else {
envBindingModel := convertCreateReqToEnvBindingModel(app, envReq)
if err := e.ds.Add(ctx, &envBindingModel); err != nil {
return nil, err
}
err := e.createEnvWorkflow(ctx, app, &envBindingModel)
if err != nil {
return nil, err
}
}
return &envReq.EnvBinding, nil
}
func (e *envBindingUsecaseImpl) BatchCreateEnvBinding(ctx context.Context, app *model.Application, envbindings apisv1.EnvBindingList) error {
for _, envBinding := range envbindings {
envBindingModel := convertToEnvBindingModel(app, *envBinding)
if err := e.ds.Add(ctx, envBindingModel); err != nil {
return err
}
err := e.createEnvWorkflow(ctx, app, envBindingModel)
if err != nil {
return err
}
}
return nil
}
func (e *envBindingUsecaseImpl) getBindingByEnv(ctx context.Context, app *model.Application, envName string) (*model.EnvBinding, error) {
var envBinding = model.EnvBinding{
AppPrimaryKey: app.PrimaryKey(),
Name: envName,
}
err := e.ds.Get(ctx, &envBinding)
if err != nil {
return nil, err
}
return &envBinding, nil
}
func (e *envBindingUsecaseImpl) UpdateEnvBinding(ctx context.Context, app *model.Application, envName string, envUpdate apisv1.PutApplicationEnvRequest) (*apisv1.DetailEnvBindingResponse, error) {
envBinding, err := e.getBindingByEnv(ctx, app, envName)
if err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrEnvBindingNotExist
}
return nil, err
}
envBindingModel := &model.EnvBinding{
Name: envBinding.Name,
Alias: envUpdate.Alias,
Description: envUpdate.Description,
TargetNames: envUpdate.TargetNames,
}
if envBinding.ComponentSelector != nil {
envBindingModel.ComponentSelector = envBinding.ComponentSelector
}
if err := e.ds.Put(ctx, envBindingModel); err != nil {
return nil, err
}
return e.DetailEnvBinding(ctx, envBindingModel)
}
func (e *envBindingUsecaseImpl) DeleteEnvBinding(ctx context.Context, app *model.Application, envName string) error {
envBinding, err := e.getBindingByEnv(ctx, app, envName)
if err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return bcode.ErrEnvBindingNotExist
}
return err
}
if err := e.ds.Delete(ctx, &model.EnvBinding{AppPrimaryKey: app.PrimaryKey(), Name: envBinding.Name}); err != nil {
return err
}
return nil
}
func (e *envBindingUsecaseImpl) BatchDeleteEnvBinding(ctx context.Context, app *model.Application) error {
envBindings, err := e.GetEnvBindings(ctx, app)
if err != nil {
return err
}
for _, envBinding := range envBindings {
if err := e.ds.Delete(ctx, &model.EnvBinding{AppPrimaryKey: app.PrimaryKey(), Name: envBinding.Name}); err != nil {
return err
}
}
return nil
}
func (e *envBindingUsecaseImpl) createEnvWorkflow(ctx context.Context, app *model.Application, env *model.EnvBinding) error {
var workflowSteps []v1beta1.WorkflowStep
for _, targetName := range env.TargetNames {
step := v1beta1.WorkflowStep{
Name: genPolicyEnvName(targetName),
Type: "deploy2env",
Properties: util.Object2RawExtension(map[string]string{
"policy": genPolicyName(env.Name),
"env": genPolicyEnvName(targetName),
}),
}
workflowSteps = append(workflowSteps, step)
}
var steps []apisv1.WorkflowStep
for _, step := range workflowSteps {
var propertyStr string
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", app.Name, step.Name, err.Error())
continue
}
propertyStr = properties.JSON()
}
steps = append(steps, apisv1.WorkflowStep{
Name: step.Name,
Type: step.Type,
DependsOn: step.DependsOn,
Properties: propertyStr,
Inputs: step.Inputs,
Outputs: step.Outputs,
})
}
_, err := e.workflowUsecase.CreateWorkflow(ctx, app, apisv1.CreateWorkflowRequest{
AppName: app.PrimaryKey(),
Name: genWorkflowName(app, env.Name),
Description: "Created automatically by envbinding.",
EnvName: env.Name,
Steps: steps,
Default: false,
})
if err != nil {
return err
}
return nil
}
func (e *envBindingUsecaseImpl) DetailEnvBinding(ctx context.Context, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error) {
return &apisv1.DetailEnvBindingResponse{
EnvBindingBase: *convertEnvbindingModelToBase(envBinding),
}, nil
}
func convertCreateReqToEnvBindingModel(app *model.Application, req apisv1.CreateApplicationEnvRequest) model.EnvBinding {
envBinding := model.EnvBinding{
AppPrimaryKey: app.Name,
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
TargetNames: req.TargetNames,
}
return envBinding
}
func convertEnvbindingModelToBase(envBinding *model.EnvBinding) *apisv1.EnvBindingBase {
ebb := &apisv1.EnvBindingBase{
Name: envBinding.Name,
Alias: envBinding.Alias,
Description: envBinding.Description,
TargetNames: envBinding.TargetNames,
ComponentSelector: (*apisv1.ComponentSelector)(envBinding.ComponentSelector),
CreateTime: envBinding.CreateTime,
UpdateTime: envBinding.UpdateTime,
}
return ebb
}
func convertToEnvBindingModel(app *model.Application, envBind apisv1.EnvBinding) *model.EnvBinding {
re := model.EnvBinding{
AppPrimaryKey: app.Name,
Name: envBind.Name,
Description: envBind.Description,
Alias: envBind.Alias,
TargetNames: envBind.TargetNames,
}
if envBind.ComponentSelector != nil {
re.ComponentSelector = (*model.ComponentSelector)(envBind.ComponentSelector)
}
return &re
}
+7
View File
@@ -110,6 +110,7 @@ func (w *workflowUsecaseImpl) CreateWorkflow(ctx context.Context, app *model.App
Name: req.Name,
Description: req.Description,
Default: req.Default,
EnvName: req.EnvName,
AppPrimaryKey: app.PrimaryKey(),
}
if err := w.ds.Add(ctx, &workflow); err != nil {
@@ -138,6 +139,7 @@ func (w *workflowUsecaseImpl) UpdateWorkflow(ctx context.Context, workflow *mode
workflow.Description = req.Description
// It is allowed to set multiple workflows as default, and only one takes effect.
workflow.Default = req.Default
workflow.EnvName = req.EnvName
if err := w.ds.Put(ctx, workflow); err != nil {
return nil, err
}
@@ -165,6 +167,7 @@ func (w *workflowUsecaseImpl) DetailWorkflow(ctx context.Context, workflow *mode
Name: workflow.Name,
Description: workflow.Description,
Default: workflow.Default,
EnvName: workflow.EnvName,
CreateTime: workflow.CreateTime,
UpdateTime: workflow.UpdateTime,
},
@@ -178,6 +181,9 @@ func (w *workflowUsecaseImpl) GetWorkflow(ctx context.Context, workflowName stri
Name: workflowName,
}
if err := w.ds.Get(ctx, &workflow); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrWorkflowNotExist
}
return nil, err
}
return &workflow, nil
@@ -199,6 +205,7 @@ func (w *workflowUsecaseImpl) ListApplicationWorkflow(ctx context.Context, app *
Name: wm.Name,
Description: wm.Description,
Default: wm.Default,
EnvName: wm.EnvName,
CreateTime: wm.CreateTime,
UpdateTime: wm.UpdateTime,
})
@@ -56,7 +56,7 @@ var ErrCreateNamespace = NewBcode(500, 10011, "auto create namespace failure")
var ErrApplicationNotExist = NewBcode(404, 10012, "application name is not exist")
// ErrApplicationNotEnv no env binding policy
var ErrApplicationNotEnv = NewBcode(404, 10013, "application not set env binding policy")
var ErrApplicationNotEnv = NewBcode(404, 10013, "application not set env binding")
// ErrApplicationEnvExist application env is exist
var ErrApplicationEnvExist = NewBcode(400, 10014, "application env is exist")
@@ -17,7 +17,10 @@ limitations under the License.
package bcode
// ErrDeliveryTargetExist deliveryTarget is exist
var ErrDeliveryTargetExist = NewBcode(400, 20006, "deliveryTarget is exist")
var ErrDeliveryTargetExist = NewBcode(400, 80001, "deliveryTarget is exist")
// ErrDeliveryTargetNotExist deliveryTarget is not exist
var ErrDeliveryTargetNotExist = NewBcode(404, 20007, "deliveryTarget is not exist")
var ErrDeliveryTargetNotExist = NewBcode(404, 80002, "deliveryTarget is not exist")
// ErrDeliveryTargetInUseCantDeleted deliveryTarget being used
var ErrDeliveryTargetInUseCantDeleted = NewBcode(404, 80003, "deliveryTarget in use, can't be deleted")
@@ -0,0 +1,32 @@
/*
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 bcode
// ErrEnvbindingDeliveryTargetNotAllExist application envbinding deliveryTarget is not exist
var ErrEnvbindingDeliveryTargetNotAllExist = NewBcode(400, 90001, "application envbinding deliveryTarget is not all exist")
// ErrFoundEnvbindingDeliveryTarget found application envbinding deliveryTarget failure
var ErrFoundEnvbindingDeliveryTarget = NewBcode(400, 90002, "found application envbinding deliveryTarget failure")
// ErrEnvBindingNotExist application envbinding is not exist
var ErrEnvBindingNotExist = NewBcode(400, 90003, "application envbinding not exist")
// ErrEnvBindingsNotExist application envbindings is not exist
var ErrEnvBindingsNotExist = NewBcode(400, 90004, "application envbinding snot exist")
// ErrEnvBindingExist application envbinding is exist
var ErrEnvBindingExist = NewBcode(400, 90005, "application envbinding is exist")
+152 -126
View File
@@ -19,6 +19,8 @@ package webservice
import (
"context"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
@@ -26,18 +28,19 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
type applicationWebService struct {
applicationUsecase usecase.ApplicationUsecase
envBindingUsecase usecase.EnvBindingUsecase
}
// NewApplicationWebService new application manage webservice
func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase) WebService {
func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase, envBindingUsecase usecase.EnvBindingUsecase) WebService {
return &applicationWebService{
applicationUsecase: applicationUsecase,
envBindingUsecase: envBindingUsecase,
}
}
@@ -86,28 +89,6 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "", bcode.Bcode{}).
Writes(apis.DetailApplicationResponse{}))
ws.Route(ws.PUT("/{name}").To(c.updateApplication).
Doc("update one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Reads(apis.UpdateApplicationRequest{}).
Returns(200, "", apis.ApplicationBase{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.ApplicationBase{}))
ws.Route(ws.PUT("/{name}/envs/{envName}").To(c.updateApplicationEnvBinding).
Doc("set application differences in the specified environment").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the application ").DataType("string")).
Reads(apis.PutApplicationEnvRequest{}).
Returns(200, "", apis.EnvBinding{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EnvBinding{}))
ws.Route(ws.GET("/{name}/envs/{envName}/status").To(c.getApplicationStatus).
Doc("get application status").
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -118,26 +99,15 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "", bcode.Bcode{}).
Writes(apis.ApplicationStatusResponse{}))
ws.Route(ws.POST("/{name}/envs").To(c.createApplicationEnv).
Doc("creating an application environment ").
ws.Route(ws.PUT("/{name}").To(c.updateApplication).
Doc("update one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationEnvRequest{}).
Returns(200, "", apis.EnvBinding{}).
Reads(apis.UpdateApplicationRequest{}).
Returns(200, "", apis.ApplicationBase{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.DELETE("/{name}/envs/{envName}").To(c.deleteApplicationEnv).
Doc("delete an application environment ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the application envbinding").DataType("string")).
Returns(200, "", apis.EmptyResponse{}).
Returns(404, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
Writes(apis.ApplicationBase{}))
ws.Route(ws.POST("/{name}/template").To(c.publishApplicationTemplate).
Doc("create one application template").
@@ -293,36 +263,52 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(200, "", apis.DetailRevisionResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.DetailRevisionResponse{}))
ws.Route(ws.GET("/{name}/envs").To(c.listApplicationEnvs).
Doc("list policy for application").
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.ListApplicationEnvBinding{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.ListApplicationEnvBinding{}))
ws.Route(ws.POST("/{name}/envs").To(c.createApplicationEnv).
Doc("creating an application environment ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationEnvRequest{}).
Returns(200, "", apis.EnvBinding{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.PUT("/{name}/envs/{envName}").To(c.updateApplicationEnv).
Doc("set application differences in the specified environment").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the envBinding ").DataType("string")).
Reads(apis.PutApplicationEnvRequest{}).
Returns(200, "", apis.EnvBinding{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EnvBinding{}))
ws.Route(ws.DELETE("/{name}/envs/{envName}").To(c.deleteApplicationEnv).
Doc("delete an application environment ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the envBinding ").DataType("string")).
Returns(200, "", apis.EmptyResponse{}).
Returns(404, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
return ws
}
func (c *applicationWebService) appCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
app, err := c.applicationUsecase.GetApplication(req.Request.Context(), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyApplication, app))
chain.ProcessFilter(req, res)
}
func (c *applicationWebService) envCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
envBinding, err := c.applicationUsecase.GetApplicationEnvBindingPolicy(req.Request.Context(), app)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
for _, env := range envBinding.Envs {
if env.Name == req.PathParameter("envName") {
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyApplicationEnvBinding, env))
chain.ProcessFilter(req, res)
return
}
}
bcode.ReturnError(req, res, bcode.ErrApplicationNotEnv)
}
func (c *applicationWebService) createApplication(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateApplicationRequest
@@ -564,29 +550,6 @@ func (c *applicationWebService) updateApplicationPolicy(req *restful.Request, re
}
}
func (c *applicationWebService) updateApplicationEnvBinding(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var updateReq apis.PutApplicationEnvRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
diff, err := c.applicationUsecase.UpdateApplicationEnvBinding(req.Request.Context(), app, req.PathParameter("envName"), updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(diff); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) updateApplication(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
@@ -610,42 +573,6 @@ func (c *applicationWebService) updateApplication(req *restful.Request, res *res
}
}
func (c *applicationWebService) createApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var createReq apis.CreateApplicationEnvRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
base, err := c.applicationUsecase.CreateApplicationEnvBinding(req.Request.Context(), app, createReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) deleteApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
err := c.applicationUsecase.DeleteApplicationEnvBinding(req.Request.Context(), app, req.PathParameter("envName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) addApplicationTrait(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
var createReq apis.CreateApplicationTraitRequest
@@ -749,3 +676,102 @@ func (c *applicationWebService) detailApplicationRevision(req *restful.Request,
return
}
}
func (c *applicationWebService) updateApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var updateReq apis.PutApplicationEnvRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
diff, err := c.envBindingUsecase.UpdateEnvBinding(req.Request.Context(), app, req.PathParameter("envName"), updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(diff); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) listApplicationEnvs(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
envBindings, err := c.envBindingUsecase.GetEnvBindings(req.Request.Context(), app)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.ListApplicationEnvBinding{EnvBindings: envBindings}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) createApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var createReq apis.CreateApplicationEnvRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
base, err := c.envBindingUsecase.CreateEnvBinding(req.Request.Context(), app, createReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) deleteApplicationEnv(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
err := c.envBindingUsecase.DeleteEnvBinding(req.Request.Context(), app, req.PathParameter("envName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) appCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
app, err := c.applicationUsecase.GetApplication(req.Request.Context(), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyApplication, app))
chain.ProcessFilter(req, res)
}
func (c *applicationWebService) envCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
envBindings, err := c.envBindingUsecase.GetEnvBindings(req.Request.Context(), app)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
for _, env := range envBindings {
if env.Name == req.PathParameter("envName") {
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyApplicationEnvBinding, env))
chain.ProcessFilter(req, res)
return
}
}
bcode.ReturnError(req, res, bcode.ErrApplicationNotEnv)
}
@@ -19,6 +19,10 @@ package webservice
import (
"context"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
@@ -31,15 +35,17 @@ import (
)
// NewDeliveryTargetWebService new deliveryTarget webservice
func NewDeliveryTargetWebService(deliveryTargetUsecase usecase.DeliveryTargetUsecase) WebService {
func NewDeliveryTargetWebService(deliveryTargetUsecase usecase.DeliveryTargetUsecase, applicationUsecase usecase.ApplicationUsecase) WebService {
return &DeliveryTargetWebService{
deliveryTargetUsecase: deliveryTargetUsecase,
applicationUsecase: applicationUsecase,
}
}
// DeliveryTargetWebService delivery target web service
type DeliveryTargetWebService struct {
deliveryTargetUsecase usecase.DeliveryTargetUsecase
applicationUsecase usecase.ApplicationUsecase
}
// GetWebService get web service
@@ -169,7 +175,20 @@ func (dt *DeliveryTargetWebService) updateDeliveryTarget(req *restful.Request, r
}
func (dt *DeliveryTargetWebService) deleteDeliveryTarget(req *restful.Request, res *restful.Response) {
if err := dt.deliveryTargetUsecase.DeleteDeliveryTarget(req.Request.Context(), req.PathParameter("name")); err != nil {
deliveryTargetName := req.PathParameter("name")
//deliveryTarget in use, can't be deleted
applications, err := dt.applicationUsecase.ListApplications(context.TODO(), apis.ListApplicatioOptions{TargetName: deliveryTargetName})
if err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
bcode.ReturnError(req, res, err)
return
}
}
if applications != nil {
bcode.ReturnError(req, res, bcode.ErrDeliveryTargetInUseCantDeleted)
return
}
if err := dt.deliveryTargetUsecase.DeleteDeliveryTarget(req.Request.Context(), deliveryTargetName); err != nil {
bcode.ReturnError(req, res, err)
return
}
@@ -185,13 +204,11 @@ func (dt *DeliveryTargetWebService) listDeliveryTargets(req *restful.Request, re
bcode.ReturnError(req, res, err)
return
}
deliveryTargets, err := dt.deliveryTargetUsecase.ListDeliveryTargets(req.Request.Context(), page, pageSize, req.QueryParameter("namespace"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(deliveryTargets); err != nil {
bcode.ReturnError(req, res, err)
return
+5 -4
View File
@@ -60,15 +60,16 @@ func returns500(b *restful.RouteBuilder) {
func Init(ds datastore.DataStore) {
clusterUsecase := usecase.NewClusterUsecase(ds)
workflowUsecase := usecase.NewWorkflowUsecase(ds)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase)
deliveryTargetUsecase := usecase.NewDeliveryTargetUsecase(ds)
namespaceUsecase := usecase.NewNamespaceUsecase()
oamApplicationUsecase := usecase.NewOAMApplicationUsecase()
velaQLUsecase := usecase.NewVelaQLUsecase()
definitionUsecase := usecase.NewDefinitionUsecase()
addonUsecase := usecase.NewAddonUsecase(ds)
deliveryTargetUsecase := usecase.NewDeliveryTargetUsecase(ds)
envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, deliveryTargetUsecase)
RegistWebService(NewClusterWebService(clusterUsecase))
RegistWebService(NewApplicationWebService(applicationUsecase))
RegistWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase))
RegistWebService(NewNamespaceWebService(namespaceUsecase))
RegistWebService(NewDefinitionWebservice(definitionUsecase))
RegistWebService(NewAddonWebService(addonUsecase))
@@ -76,6 +77,6 @@ func Init(ds datastore.DataStore) {
RegistWebService(NewOAMApplication(oamApplicationUsecase))
RegistWebService(&policyDefinitionWebservice{})
RegistWebService(NewWorkflowWebService(workflowUsecase, applicationUsecase))
RegistWebService(NewDeliveryTargetWebService(deliveryTargetUsecase))
RegistWebService(NewDeliveryTargetWebService(deliveryTargetUsecase, applicationUsecase))
RegistWebService(NewVelaQLWebService(velaQLUsecase))
}
@@ -59,7 +59,6 @@ var _ = Describe("Test application rest api", func() {
Expect(cmp.Diff(appBase.Description, req.Description)).Should(BeEmpty())
Expect(cmp.Diff(appBase.Namespace, req.Namespace)).Should(BeEmpty())
Expect(cmp.Diff(appBase.Labels["test"], req.Labels["test"])).Should(BeEmpty())
Expect(cmp.Diff(appBase.EnvBinding[0].Name, "dev-env")).Should(BeEmpty())
})
It("Test delete app", func() {