Feat: add api for app compare、reset、dryrun (#3136)

* Feat: add api for app compare or reset

Signed-off-by: zxbyoyoyo <596908030@qq.com>

* Fix: fix lint

Signed-off-by: zxbyoyoyo <596908030@qq.com>

* Fix: fix lint

Signed-off-by: zxbyoyoyo <596908030@qq.com>

* Fix: fix imports

Signed-off-by: zxbyoyoyo <596908030@qq.com>
This commit is contained in:
朱晓兵
2022-02-17 13:58:48 +08:00
committed by GitHub
parent 8eaa438631
commit debd9f2b40
10 changed files with 658 additions and 79 deletions
+1
View File
@@ -21,6 +21,7 @@ COPY cmd/apiserver/main.go cmd/apiserver/main.go
COPY apis/ apis/
COPY pkg/ pkg/
COPY version/ version/
COPY references/ references/
# Build
ARG TARGETARCH
+1
View File
@@ -17,6 +17,7 @@ COPY cmd/apiserver/main.go cmd/apiserver/main.go
COPY apis/ apis/
COPY pkg/ pkg/
COPY version/ version/
COPY references/ references/
# Build
ARG TARGETARCH
+1
View File
@@ -18,6 +18,7 @@ COPY cmd/ cmd/
COPY apis/ apis/
COPY pkg/ pkg/
COPY version/ version/
COPY references/ references/
# Build
ARG TARGETARCH
+31
View File
@@ -305,6 +305,37 @@ type ApplicationBase struct {
Labels map[string]string `json:"labels,omitempty"`
}
// AppCompareResponse application compare result
type AppCompareResponse struct {
IsDiff bool `json:"isDiff"`
DiffReport string `json:"diffReport"`
NewAppYAML string `json:"newAppYAML"`
OldAppYAML string `json:"oldAppYAML"`
}
// AppResetResponse application reset result
type AppResetResponse struct {
IsReset bool `json:"isReset"`
}
// AppCompareReq application compare req
type AppCompareReq struct {
Env string `json:"env"`
}
// AppDryRunReq application dry-run req
type AppDryRunReq struct {
AppName string `json:"appName"`
DryRunType string `json:"dryRunType"`
Env string `json:"env"`
Version string `json:"version"`
}
// AppDryRunResponse application dry-run result
type AppDryRunResponse struct {
YAML string `json:"yaml"`
}
// ApplicationStatusResponse application status response body
type ApplicationStatusResponse struct {
EnvName string `json:"envName"`
+1 -1
View File
@@ -398,7 +398,7 @@ func (u *defaultAddonHandler) UpdateAddon(ctx context.Context, name string, args
var app v1beta1.Application
// check addon application whether exist
err := u.kubeClient.Get(context.Background(), client.ObjectKey{
err := u.kubeClient.Get(ctx, client.ObjectKey{
Namespace: types.DefaultKubeVelaNS,
Name: pkgaddon.Convert2AppName(name),
}, &app)
+313
View File
@@ -17,6 +17,7 @@ limitations under the License.
package usecase
import (
"bytes"
"context"
"errors"
"fmt"
@@ -25,6 +26,8 @@ import (
"strings"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -41,12 +44,16 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
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"
"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"
"github.com/oam-dev/kubevela/references/appfile/dryrun"
)
// PolicyType build-in policy type
@@ -91,6 +98,9 @@ type ApplicationUsecase interface {
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
@@ -1348,6 +1358,78 @@ func (c *applicationUsecaseImpl) Statistics(ctx context.Context, app *model.Appl
}, 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 {
@@ -1419,3 +1501,234 @@ func genWebhookToken() string {
}
return string(b)
}
func convertToModelComponent(appPrimaryKey string, component common.ApplicationComponent) (model.ApplicationComponent, error) {
bc := model.ApplicationComponent{
AppPrimaryKey: appPrimaryKey,
Name: component.Name,
Type: component.Type,
ExternalRevision: component.ExternalRevision,
DependsOn: component.DependsOn,
Inputs: component.Inputs,
Outputs: component.Outputs,
Scopes: component.Scopes,
}
if component.Properties != nil {
properties, err := model.NewJSONStruct(component.Properties)
if err != nil {
return bc, err
}
bc.Properties = properties
}
for _, trait := range component.Traits {
properties, err := model.NewJSONStruct(trait.Properties)
if err != nil {
return bc, err
}
bc.Traits = append(bc.Traits, model.ApplicationTrait{CreateTime: time.Now(), UpdateTime: time.Now(), Properties: properties, Type: trait.Type, Alias: trait.Type, Description: "auto gen"})
}
return bc, nil
}
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.ErrApplicationComponetNotExist
}
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 := compareSlices(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 := convertToModelComponent(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, 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, 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
}
+116 -3
View File
@@ -39,6 +39,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
v1 "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"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/apply"
@@ -56,7 +57,7 @@ var _ = Describe("Test application usecase function", func() {
testProject = "app-project"
testApp = "test-app"
defaultTarget = "default"
namespace1 = "app-test1"
namespace1 = "app-test2"
envnsdev = "envnsdev"
envnstest = "envnstest"
)
@@ -67,7 +68,7 @@ var _ = Describe("Test application usecase function", func() {
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
workflowUsecase = &workflowUsecaseImpl{ds: ds, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient, caches: make(map[string]*utils.MemoryCache)}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, envUsecase: envUsecase, workflowUsecase: workflowUsecase, kubeClient: k8sClient, definitionUsecase: definitionUsecase}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
@@ -82,7 +83,6 @@ var _ = Describe("Test application usecase function", func() {
targetUsecase: targetUsecase,
projectUsecase: projectUsecase,
}
})
It("Test CreateApplication function", func() {
@@ -506,6 +506,119 @@ var _ = Describe("Test application usecase function", func() {
Expect(err).Should(BeNil())
})
It("Test CompareAppWithLatestRevision function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
_, err = appUsecase.Deploy(context.TODO(), appModel, v1.ApplicationDeployRequest{WorkflowName: convertWorkflowName("app-dev")})
Expect(err).Should(BeNil())
component, err := appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name")
Expect(err).Should(BeNil())
By("compare when app not change, should return false")
compareResponse, err := appUsecase.CompareAppWithLatestRevision(context.TODO(), appModel, v1.AppCompareReq{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, false)).Should(BeEmpty())
By("compare when app not change and env not empty, should return false")
compareResponse, err = appUsecase.CompareAppWithLatestRevision(context.TODO(), appModel, v1.AppCompareReq{Env: "app-dev"})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, false)).Should(BeEmpty())
By("compare when app add env, not change, should return false")
_, err = envUsecase.CreateEnv(context.TODO(), v1.CreateEnvRequest{Name: "app-prod", Namespace: "envnsprod", Targets: []string{defaultTarget}, Project: "app-prod"})
Expect(err).Should(BeNil())
_, err = envBindingUsecase.CreateEnvBinding(context.TODO(), appModel, v1.CreateApplicationEnvbindingRequest{EnvBinding: v1.EnvBinding{Name: "app-prod"}})
Expect(err).Should(BeNil())
compareResponse, err = appUsecase.CompareAppWithLatestRevision(context.TODO(), appModel, v1.AppCompareReq{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, false)).Should(BeEmpty())
By("compare when app's env add target, should return true")
_, err = targetUsecase.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
Expect(err).Should(BeNil())
_, err = envUsecase.UpdateEnv(context.TODO(), "app-dev",
v1.UpdateEnvRequest{
Description: "this is a env description update",
Targets: []string{defaultTarget, "dev-target1"},
})
Expect(err).Should(BeNil())
compareResponse, err = appUsecase.CompareAppWithLatestRevision(context.TODO(), appModel, v1.AppCompareReq{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, true)).Should(BeEmpty())
By("compare when update app's trait, should return true")
// reset app config
_, err = appUsecase.ResetAppToLatestRevision(context.TODO(), testApp)
Expect(err).Should(BeNil())
_, err = appUsecase.UpdateApplicationTrait(context.TODO(), appModel, &model.ApplicationComponent{Name: "component-name"}, "scaler", v1.UpdateApplicationTraitRequest{
Properties: `{"replicas":2}`,
Alias: "alias",
Description: "description",
})
Expect(err).Should(BeNil())
compareResponse, err = appUsecase.CompareAppWithLatestRevision(context.TODO(), appModel, v1.AppCompareReq{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, true)).Should(BeEmpty())
By("compare when update component's target after app deployed ,should return ture")
// reset app config
_, err = appUsecase.ResetAppToLatestRevision(context.TODO(), testApp)
Expect(err).Should(BeNil())
newProperties := "{\"exposeType\":\"NodePort\",\"image\":\"nginx\",\"imagePullPolicy\":\"Always\"}"
_, err = appUsecase.UpdateComponent(context.TODO(),
appModel,
component,
v1.UpdateApplicationComponentRequest{
Properties: &newProperties,
})
Expect(err).Should(BeNil())
compareResponse, err = appUsecase.CompareAppWithLatestRevision(context.TODO(), appModel, v1.AppCompareReq{})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, true)).Should(BeEmpty())
err = envBindingUsecase.ApplicationEnvRecycle(context.TODO(), &model.Application{Name: testApp}, &model.EnvBinding{Name: "app-dev"})
Expect(err).Should(BeNil())
})
It("Test ResetAppToLatestRevision function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
resetResponse, err := appUsecase.ResetAppToLatestRevision(context.TODO(), testApp)
Expect(err).Should(BeNil())
Expect(cmp.Diff(resetResponse.IsReset, true)).Should(BeEmpty())
component, err := appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name")
Expect(err).Should(BeNil())
expectProperties := "{\"image\":\"nginx\"}"
Expect(cmp.Diff(component.Properties.JSON(), expectProperties)).Should(BeEmpty())
})
It("Test DryRun with app function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
resetResponse, err := appUsecase.DryRunAppOrRevision(context.TODO(), appModel, v1.AppDryRunReq{DryRunType: "APP"})
Expect(err).Should(BeNil())
Expect(strings.Contains(resetResponse.YAML, "# Application(test-app)")).Should(BeTrue())
Expect(strings.Contains(resetResponse.YAML, "# Application(test-app) -- Component(component-name)")).Should(BeTrue())
})
It("Test DryRun with env revision function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
resetResponse, err := appUsecase.DryRunAppOrRevision(context.TODO(), appModel, v1.AppDryRunReq{DryRunType: "Revision", Env: "app-dev"})
Expect(err).Should(BeNil())
Expect(strings.Contains(resetResponse.YAML, "# Application(test-app)")).Should(BeTrue())
Expect(strings.Contains(resetResponse.YAML, "# Application(test-app) -- Component(component-name)")).Should(BeTrue())
})
It("Test DryRun with last revision function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
resetResponse, err := appUsecase.DryRunAppOrRevision(context.TODO(), appModel, v1.AppDryRunReq{DryRunType: "Revision"})
Expect(err).Should(BeNil())
Expect(strings.Contains(resetResponse.YAML, "# Application(test-app)")).Should(BeTrue())
Expect(strings.Contains(resetResponse.YAML, "# Application(test-app) -- Component(component-name)")).Should(BeTrue())
})
It("Test DeleteApplication function", func() {
appModel, err := appUsecase.GetApplication(context.TODO(), testApp)
Expect(err).Should(BeNil())
+75 -75
View File
@@ -102,6 +102,15 @@
label: ReadinessProbe
sort: 13
subParameters:
- description: Minimum consecutive successes for the probe to be considered successful
after having failed.
jsonKey: successThreshold
label: SuccessThreshold
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
- description: Instructions for assessing container health by probing a TCP socket.
Either this attribute or the exec attribute or the httpGet attribute MUST be
specified. This attribute is mutually exclusive with both the exec attribute
@@ -221,15 +230,6 @@
validate:
defaultValue: 10
required: true
- description: Minimum consecutive successes for the probe to be considered successful
after having failed.
jsonKey: successThreshold
label: SuccessThreshold
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
uiType: Group
validate: {}
- description: Instructions for assessing whether the container is alive.
@@ -237,43 +237,6 @@
label: LivenessProbe
sort: 15
subParameters:
- description: Number of seconds after which the probe times out.
jsonKey: timeoutSeconds
label: TimeoutSeconds
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
- description: Instructions for assessing container health by executing a command.
Either this attribute or the httpGet attribute or the tcpSocket attribute MUST
be specified. This attribute is mutually exclusive with both the httpGet attribute
and the tcpSocket attribute.
jsonKey: exec
label: Exec
sort: 100
subParameters:
- description: A command to be executed inside the container to assess its health.
Each space delimited token of the command is a separate array element. Commands
exiting 0 are considered to be successful probes, whilst all other exit codes
are considered failures.
jsonKey: command
label: Command
sort: 100
uiType: Strings
validate:
required: true
uiType: Group
validate: {}
- description: Number of consecutive failures required to determine the container
is not alive (liveness probe) or not ready (readiness probe).
jsonKey: failureThreshold
label: FailureThreshold
sort: 100
uiType: Number
validate:
defaultValue: 3
required: true
- description: Instructions for assessing container health by executing an HTTP
GET request. Either this attribute or the exec attribute or the tcpSocket attribute
MUST be specified. This attribute is mutually exclusive with both the exec attribute
@@ -282,22 +245,6 @@
label: HttpGet
sort: 100
subParameters:
- description: The endpoint, relative to the port, to which the HTTP GET request
should be directed.
jsonKey: path
label: Path
sort: 100
uiType: Input
validate:
required: true
- description: The TCP socket within the container to which the HTTP GET request
should be directed.
jsonKey: port
label: Port
sort: 100
uiType: Number
validate:
required: true
- description: ""
jsonKey: httpHeaders
label: HttpHeaders
@@ -319,6 +266,22 @@
required: true
uiType: Structs
validate: {}
- description: The endpoint, relative to the port, to which the HTTP GET request
should be directed.
jsonKey: path
label: Path
sort: 100
uiType: Input
validate:
required: true
- description: The TCP socket within the container to which the HTTP GET request
should be directed.
jsonKey: port
label: Port
sort: 100
uiType: Number
validate:
required: true
uiType: Group
validate: {}
- description: Number of seconds after the container is started before the first
@@ -365,14 +328,45 @@
required: true
uiType: Group
validate: {}
- description: Number of seconds after which the probe times out.
jsonKey: timeoutSeconds
label: TimeoutSeconds
sort: 100
uiType: Number
validate:
defaultValue: 1
required: true
- description: Instructions for assessing container health by executing a command.
Either this attribute or the httpGet attribute or the tcpSocket attribute MUST
be specified. This attribute is mutually exclusive with both the httpGet attribute
and the tcpSocket attribute.
jsonKey: exec
label: Exec
sort: 100
subParameters:
- description: A command to be executed inside the container to assess its health.
Each space delimited token of the command is a separate array element. Commands
exiting 0 are considered to be successful probes, whilst all other exit codes
are considered failures.
jsonKey: command
label: Command
sort: 100
uiType: Strings
validate:
required: true
uiType: Group
validate: {}
- description: Number of consecutive failures required to determine the container
is not alive (liveness probe) or not ready (readiness probe).
jsonKey: failureThreshold
label: FailureThreshold
sort: 100
uiType: Number
validate:
defaultValue: 3
required: true
uiType: Group
validate: {}
- description: Specify image pull secrets for your service
jsonKey: imagePullSecrets
label: ImagePullSecrets
sort: 100
uiType: Strings
validate: {}
- description: Which port do you want customer traffic sent to
disable: true
jsonKey: port
@@ -388,13 +382,6 @@
label: Volumes
sort: 100
subParameters:
- description: ""
jsonKey: mountPath
label: MountPath
sort: 100
uiType: Input
validate:
required: true
- description: ""
jsonKey: name
label: Name
@@ -418,6 +405,13 @@
- label: EmptyDir
value: emptyDir
required: true
- description: ""
jsonKey: mountPath
label: MountPath
sort: 100
uiType: Input
validate:
required: true
uiType: Structs
validate: {}
- description: If addRevisionLabel is true, the appRevision label will be added to
@@ -430,3 +424,9 @@
validate:
defaultValue: false
required: true
- description: Specify image pull secrets for your service
jsonKey: imagePullSecrets
label: ImagePullSecrets
sort: 100
uiType: Strings
validate: {}
@@ -495,6 +495,33 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "", bcode.Bcode{}).
Writes(apis.ListWorkflowRecordsResponse{}))
ws.Route(ws.POST("/{name}/compare").To(c.compareAppWithLatestRevision).
Doc("compare application with env latest revision").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Returns(200, "", apis.ApplicationBase{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.AppCompareResponse{}))
ws.Route(ws.POST("/{name}/reset").To(c.resetAppToLatestRevision).
Doc("reset application to latest revision").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Returns(200, "", apis.AppResetResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.AppResetResponse{}))
ws.Route(ws.POST("/{name}/dry-run").To(c.dryRunAppOrRevision).
Doc("dry-run application to latest revision").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Returns(200, "", apis.AppDryRunResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.AppDryRunResponse{}))
return ws
}
@@ -1076,3 +1103,68 @@ func (c *applicationWebService) listApplicationRecords(req *restful.Request, res
return
}
}
func (c *applicationWebService) compareAppWithLatestRevision(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var compareReq apis.AppCompareReq
if err := req.ReadEntity(&compareReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&compareReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
base, err := c.applicationUsecase.CompareAppWithLatestRevision(req.Request.Context(), app, compareReq)
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) resetAppToLatestRevision(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
base, err := c.applicationUsecase.ResetAppToLatestRevision(req.Request.Context(), app.Name)
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) dryRunAppOrRevision(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
// Verify the validity of parameters
var dryRunReq apis.AppDryRunReq
if err := req.ReadEntity(&dryRunReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&dryRunReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if dryRunReq.AppName == "" {
dryRunReq.AppName = app.Name
}
base, err := c.applicationUsecase.DryRunAppOrRevision(req.Request.Context(), app, dryRunReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
+27
View File
@@ -112,6 +112,33 @@ func (l *LiveDiffOption) Diff(ctx context.Context, app *v1beta1.Application, app
return diffResult, nil
}
// DiffApps does three phases, dry-run on input app, preparing manifest for diff, and
// calculating diff on manifests.
func (l *LiveDiffOption) DiffApps(ctx context.Context, app *v1beta1.Application, oldApp *v1beta1.Application) (*DiffEntry, error) {
comps, err := l.ExecuteDryRun(ctx, app)
if err != nil {
return nil, errors.WithMessagef(err, "cannot dry-run for app %q", app.Name)
}
// new refers to the app as input to dry-run
newManifest, err := generateManifest(app, comps)
if err != nil {
return nil, errors.WithMessagef(err, "cannot generate diff manifest for app %q", app.Name)
}
oldComps, err := l.ExecuteDryRun(ctx, oldApp)
if err != nil {
return nil, errors.WithMessagef(err, "cannot dry-run for app %q", oldApp.Name)
}
// new refers to the app as input to dry-run
oldManifest, err := generateManifest(oldApp, oldComps)
if err != nil {
return nil, errors.WithMessagef(err, "cannot generate diff manifest for app %q", oldApp.Name)
}
diffResult := l.calculateDiff(oldManifest, newManifest)
return diffResult, nil
}
// calculateDiff calculate diff between two application and their sub-resources
func (l *LiveDiffOption) calculateDiff(oldApp, newApp *manifest) *DiffEntry {
emptyManifest := &manifest{}