Feat: add app revision list and detail api (#2722)

* Feat: add app revision list and detail api

* add envName and status filter

* make swagger doc

* revert ui-schema
This commit is contained in:
Tianxin Dong
2021-11-17 15:29:36 +08:00
committed by GitHub
parent ba4a28fa0d
commit 2fc0f1cd2b
9 changed files with 499 additions and 54 deletions
+287 -36
View File
@@ -1305,6 +1305,113 @@
}
}
},
"/api/v1/applications/{name}/revisions": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"application"
],
"summary": "list revisions for application",
"operationId": "listApplicationRevisions",
"parameters": [
{
"type": "string",
"description": "identifier of the application ",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"description": "query identifier of the env",
"name": "envName",
"in": "query"
},
{
"type": "string",
"description": "query identifier of the status",
"name": "status",
"in": "query"
},
{
"type": "integer",
"description": "query the page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "query the page size number",
"name": "pageSize",
"in": "query"
}
],
"responses": {
"200": {
"schema": {
"$ref": "#/definitions/v1.ListRevisionsResponse"
}
},
"400": {
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/applications/{name}/revisions/{revision}": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"application"
],
"summary": "detail revision for application",
"operationId": "detailApplicationRevision",
"parameters": [
{
"type": "string",
"description": "identifier of the application",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the application revision",
"name": "revision",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"schema": {
"$ref": "#/definitions/v1.DetailRevisionResponse"
}
},
"400": {
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/applications/{name}/template": {
"post": {
"consumes": [
@@ -1857,9 +1964,9 @@
"parameters": [
{
"enum": [
"workflowstep",
"component",
"trait",
"workflowstep"
"trait"
],
"type": "string",
"description": "query the definition type",
@@ -2477,17 +2584,15 @@
},
{
"type": "integer",
"description": "Query the page number.",
"description": "query the page number",
"name": "page",
"in": "path",
"required": true
"in": "query"
},
{
"type": "integer",
"description": "Query the page size number.",
"description": "query the page size number",
"name": "pageSize",
"in": "path",
"required": true
"in": "query"
}
],
"responses": {
@@ -2744,11 +2849,11 @@
},
"common.AppRolloutStatus": {
"required": [
"upgradedReadyReplicas",
"rollingState",
"batchRollingState",
"currentBatch",
"upgradedReplicas",
"upgradedReadyReplicas",
"lastTargetAppRevision"
],
"properties": {
@@ -3320,6 +3425,61 @@
}
}
},
"model.ApplicationRevision": {
"required": [
"createTime",
"updateTime",
"appPrimaryKey",
"version",
"status",
"reason",
"deployUser",
"note",
"triggerType",
"workflowName",
"envName"
],
"properties": {
"appPrimaryKey": {
"type": "string"
},
"applyAppConfig": {
"type": "string"
},
"createTime": {
"type": "string",
"format": "date-time"
},
"deployUser": {
"type": "string"
},
"envName": {
"type": "string"
},
"note": {
"type": "string"
},
"reason": {
"type": "string"
},
"status": {
"type": "string"
},
"triggerType": {
"type": "string"
},
"updateTime": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "string"
},
"workflowName": {
"type": "string"
}
}
},
"model.ApplicationTrait": {
"required": [
"alias",
@@ -3778,17 +3938,26 @@
},
"v1.ApplicationDeployResponse": {
"required": [
"deployUser",
"note",
"triggerType",
"createTime",
"version",
"status",
"reason"
"reason",
"deployUser",
"note",
"envName",
"triggerType"
],
"properties": {
"createTime": {
"type": "string",
"format": "date-time"
},
"deployUser": {
"type": "string"
},
"envName": {
"type": "string"
},
"note": {
"type": "string"
},
@@ -3863,17 +4032,26 @@
},
"v1.ApplicationRevisionBase": {
"required": [
"createTime",
"version",
"status",
"reason",
"deployUser",
"note",
"envName",
"triggerType"
],
"properties": {
"createTime": {
"type": "string",
"format": "date-time"
},
"deployUser": {
"type": "string"
},
"envName": {
"type": "string"
},
"note": {
"type": "string"
},
@@ -4625,10 +4803,10 @@
},
"v1.DetailAddonResponse": {
"required": [
"version",
"description",
"icon",
"name",
"version",
"schema",
"uiSchema"
],
@@ -4679,13 +4857,13 @@
},
"v1.DetailApplicationResponse": {
"required": [
"icon",
"name",
"namespace",
"description",
"createTime",
"updateTime",
"name",
"icon",
"alias",
"namespace",
"policies",
"status",
"resourceInfo",
@@ -4749,20 +4927,20 @@
},
"v1.DetailClusterResponse": {
"required": [
"kubeConfigSecret",
"description",
"createTime",
"alias",
"icon",
"status",
"reason",
"provider",
"dashboardURL",
"updateTime",
"name",
"labels",
"status",
"description",
"apiServerURL",
"kubeConfig",
"createTime",
"updateTime",
"alias",
"reason",
"name",
"kubeConfigSecret",
"resourceInfo"
],
"properties": {
@@ -4820,13 +4998,13 @@
},
"v1.DetailComponentResponse": {
"required": [
"createTime",
"name",
"alias",
"type",
"appPrimaryKey",
"creator",
"name",
"type",
"createTime",
"updateTime",
"appPrimaryKey"
"alias"
],
"properties": {
"alias": {
@@ -4921,10 +5099,10 @@
},
"v1.DetailDeliveryTargetResponse": {
"required": [
"createTime",
"updateTime",
"name",
"namespace"
"namespace",
"createTime"
],
"properties": {
"alias": {
@@ -4957,13 +5135,13 @@
},
"v1.DetailPolicyResponse": {
"required": [
"createTime",
"updateTime",
"name",
"type",
"description",
"creator",
"properties",
"createTime",
"updateTime"
"properties"
],
"properties": {
"createTime": {
@@ -4991,12 +5169,67 @@
}
}
},
"v1.DetailRevisionResponse": {
"required": [
"triggerType",
"workflowName",
"createTime",
"appPrimaryKey",
"reason",
"deployUser",
"note",
"updateTime",
"version",
"status",
"envName"
],
"properties": {
"appPrimaryKey": {
"type": "string"
},
"applyAppConfig": {
"type": "string"
},
"createTime": {
"type": "string",
"format": "date-time"
},
"deployUser": {
"type": "string"
},
"envName": {
"type": "string"
},
"note": {
"type": "string"
},
"reason": {
"type": "string"
},
"status": {
"type": "string"
},
"triggerType": {
"type": "string"
},
"updateTime": {
"type": "string",
"format": "date-time"
},
"version": {
"type": "string"
},
"workflowName": {
"type": "string"
}
}
},
"v1.DetailWorkflowRecordResponse": {
"required": [
"terminated",
"name",
"namespace",
"suspend",
"terminated",
"deployTime",
"deployUser",
"note",
@@ -5292,6 +5525,24 @@
}
}
},
"v1.ListRevisionsResponse": {
"required": [
"revisions",
"total"
],
"properties": {
"revisions": {
"type": "array",
"items": {
"$ref": "#/definitions/v1.ApplicationRevisionBase"
}
},
"total": {
"type": "integer",
"format": "int64"
}
}
},
"v1.ListWorkflowRecordsResponse": {
"required": [
"records",
+5
View File
@@ -216,6 +216,8 @@ type ApplicationRevision struct {
// WorkflowName deploy controller by workflow
WorkflowName string `json:"workflowName"`
// EnvName is the env name of this application revision
EnvName string `json:"envName"`
}
// TableName return custom table name
@@ -249,5 +251,8 @@ func (a *ApplicationRevision) Index() map[string]string {
if a.TriggerType != "" {
index["triggerType"] = a.TriggerType
}
if a.EnvName != "" {
index["envName"] = a.EnvName
}
return index
}
+18 -5
View File
@@ -676,11 +676,24 @@ type DeliveryTargetBase struct {
// ApplicationRevisionBase application revision base spec
type ApplicationRevisionBase struct {
Version string `json:"version"`
Status string `json:"status"`
Reason string `json:"reason"`
DeployUser string `json:"deployUser"`
Note string `json:"note"`
CreateTime time.Time `json:"createTime"`
Version string `json:"version"`
Status string `json:"status"`
Reason string `json:"reason"`
DeployUser string `json:"deployUser"`
Note string `json:"note"`
EnvName string `json:"envName"`
// SourceType the event trigger source, Web or API
TriggerType string `json:"triggerType"`
}
// ListRevisionsResponse list application revisions
type ListRevisionsResponse struct {
Revisions []ApplicationRevisionBase `json:"revisions"`
Total int64 `json:"total"`
}
// DetailRevisionResponse get application revision detail
type DetailRevisionResponse struct {
model.ApplicationRevision
}
+71 -7
View File
@@ -84,6 +84,8 @@ type ApplicationUsecase interface {
CreateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.CreateApplicationTraitRequest) (*apisv1.ApplicationTrait, error)
DeleteApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string) error
UpdateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string, req apisv1.UpdateApplicationTraitRequest) (*apisv1.ApplicationTrait, error)
ListRevisions(ctx context.Context, appName, envName, status string, page, pageSize int) (*apisv1.ListRevisionsResponse, error)
DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error)
}
type applicationUsecaseImpl struct {
@@ -583,6 +585,11 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
}
}
workflow, err := c.workflowUsecase.GetWorkflow(ctx, oamApp.Annotations[oam.AnnotationWorkflowName])
if err != nil {
return nil, err
}
var appRevision = &model.ApplicationRevision{
AppPrimaryKey: app.PrimaryKey(),
Version: version,
@@ -593,6 +600,7 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
Note: req.Note,
TriggerType: req.TriggerType,
WorkflowName: oamApp.Annotations[oam.AnnotationWorkflowName],
EnvName: workflow.EnvName,
}
if err := c.ds.Add(ctx, appRevision); err != nil {
@@ -636,23 +644,23 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
}, nil
}
func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMoel *model.Application, reqWorkflowName, version string) (*v1beta1.Application, error) {
func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, version string) (*v1beta1.Application, error) {
var app = &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: appMoel.Name,
Namespace: appMoel.Namespace,
Labels: appMoel.Labels,
Name: appModel.Name,
Namespace: appModel.Namespace,
Labels: appModel.Labels,
Annotations: map[string]string{
oam.AnnotationDeployVersion: version,
},
},
}
var component = model.ApplicationComponent{
AppPrimaryKey: appMoel.PrimaryKey(),
AppPrimaryKey: appModel.PrimaryKey(),
}
components, err := c.ds.List(ctx, &component, &datastore.ListOptions{})
if err != nil {
@@ -663,7 +671,7 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
}
var policy = model.ApplicationPolicy{
AppPrimaryKey: appMoel.PrimaryKey(),
AppPrimaryKey: appModel.PrimaryKey(),
}
policies, err := c.ds.List(ctx, &policy, &datastore.ListOptions{})
if err != nil {
@@ -720,7 +728,7 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
return nil, err
}
} else {
workflow, err = c.workflowUsecase.GetApplicationDefaultWorkflow(ctx, appMoel)
workflow, err = c.workflowUsecase.GetApplicationDefaultWorkflow(ctx, appModel)
if err != nil && !errors.Is(err, bcode.ErrWorkflowNoDefault) {
return nil, err
}
@@ -1171,6 +1179,62 @@ func (c *applicationUsecaseImpl) UpdateApplicationTrait(ctx context.Context, app
return nil, bcode.ErrTraitNotExist
}
func (c *applicationUsecaseImpl) ListRevisions(ctx context.Context, appName, envName, status string, page, pageSize int) (*apisv1.ListRevisionsResponse, error) {
var revision = model.ApplicationRevision{
AppPrimaryKey: appName,
}
if envName != "" {
revision.EnvName = envName
}
if status != "" {
revision.Status = status
}
revisions, err := c.ds.List(ctx, &revision, &datastore.ListOptions{Page: page, PageSize: pageSize})
if err != nil {
return nil, err
}
resp := &apisv1.ListRevisionsResponse{
Revisions: []apisv1.ApplicationRevisionBase{},
}
for _, raw := range revisions {
r, ok := raw.(*model.ApplicationRevision)
if ok {
resp.Revisions = append(resp.Revisions, apisv1.ApplicationRevisionBase{
CreateTime: r.CreateTime,
Version: r.Version,
Status: r.Status,
Reason: r.Reason,
DeployUser: r.DeployUser,
Note: r.Note,
EnvName: r.EnvName,
TriggerType: r.TriggerType,
})
}
}
count, err := c.ds.Count(ctx, &revision)
if err != nil {
return nil, err
}
resp.Total = count
return resp, nil
}
func (c *applicationUsecaseImpl) DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error) {
var revision = model.ApplicationRevision{
AppPrimaryKey: appName,
Version: revisionName,
}
if err := c.ds.Get(ctx, &revision); err != nil {
return nil, err
}
return &apisv1.DetailRevisionResponse{
ApplicationRevision: revision,
}, nil
}
func createEnvBind(envBind apisv1.EnvBinding) v1alpha1.EnvConfig {
placement := v1alpha1.EnvPlacement{}
var componentSelector *v1alpha1.EnvSelector
@@ -18,6 +18,7 @@ package usecase
import (
"context"
"fmt"
"io/ioutil"
"strings"
@@ -516,4 +517,48 @@ var _ = Describe("Test application usecase function", func() {
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(policies), 0)).Should(BeEmpty())
})
It("Test ListRevisions function", func() {
for i := 0; i < 3; i++ {
appModel := &model.ApplicationRevision{
AppPrimaryKey: "test-app",
Version: fmt.Sprintf("%d", i),
EnvName: fmt.Sprintf("env-%d", i),
Status: model.RevisionStatusRunning,
}
if i == 0 {
appModel.Status = model.RevisionStatusTerminated
}
err := workflowUsecase.createTestApplicationRevision(context.TODO(), appModel)
Expect(err).Should(BeNil())
}
revisions, err := appUsecase.ListRevisions(context.TODO(), "test-app", "", "", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(3)))
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app", "env-0", "", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(1)))
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app", "", "terminated", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(1)))
revisions, err = appUsecase.ListRevisions(context.TODO(), "test-app", "env-1", "terminated", 0, 10)
Expect(err).Should(BeNil())
Expect(revisions.Total).Should(Equal(int64(0)))
})
It("Test DetailRevisions function", func() {
err := workflowUsecase.createTestApplicationRevision(context.TODO(), &model.ApplicationRevision{
AppPrimaryKey: "test-app",
Version: "123",
DeployUser: "test-user",
})
Expect(err).Should(BeNil())
revision, err := appUsecase.DetailRevision(context.TODO(), "test-app", "123")
Expect(err).Should(BeNil())
Expect(revision.Version).Should(Equal("123"))
Expect(revision.DeployUser).Should(Equal("test-user"))
})
})
+16 -4
View File
@@ -311,12 +311,27 @@ func (w *workflowUsecaseImpl) SyncWorkflowRecord(ctx context.Context) error {
continue
}
if app.Annotations == nil {
klog.ErrorS(err, "empty application annotation", "controller revision name", cr.Name)
continue
}
if _, ok := app.Annotations[oam.AnnotationWorkflowName]; !ok {
klog.ErrorS(err, "missing application workflow name", "controller revision name", cr.Name)
continue
}
revisionName, ok := app.Annotations[oam.AnnotationDeployVersion]
if !ok {
klog.ErrorS(err, "failed to get application revision name", "controller revision name", cr.Name)
continue
}
if err := w.createWorkflowRecord(ctx, app, strings.TrimPrefix(cr.Name, "record-")); err != nil && !errors.Is(err, datastore.ErrRecordExist) {
klog.ErrorS(err, "failed to create workflow record", "controller revision name", cr.Name)
continue
}
err = w.updateRecordApplicationRevisionStatus(ctx, app.Name, strings.TrimPrefix(cr.Name, fmt.Sprintf("record-%s-", app.Name)), app.Status.Workflow.Terminated)
err = w.updateRecordApplicationRevisionStatus(ctx, app.Name, revisionName, app.Status.Workflow.Terminated)
if err != nil && !errors.Is(err, datastore.ErrRecordNotExist) {
klog.ErrorS(err, "failed to update deploy event status", "controller revision name", cr.Name)
continue
@@ -354,9 +369,6 @@ func (w *workflowUsecaseImpl) updateRecordApplicationRevisionStatus(ctx context.
}
func (w *workflowUsecaseImpl) createWorkflowRecord(ctx context.Context, app *v1beta1.Application, revisionName string) error {
if app.Annotations == nil || app.Annotations[oam.AnnotationWorkflowName] == "" {
return fmt.Errorf("missing workflow name")
}
status := app.Status.Workflow
return w.ds.Add(ctx, &model.WorkflowRecord{
@@ -170,6 +170,7 @@ kind: Application
metadata:
annotations:
app.oam.dev/workflowName: test-workflow-name
app.oam.dev/deployVersion: "1234"
name: test
namespace: default
spec:
@@ -26,6 +26,7 @@ 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"
)
@@ -269,6 +270,29 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(200, "", apis.ApplicationTrait{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}/revisions").To(c.listApplicationRevisions).
Doc("list revisions for application").
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.QueryParameter("envName", "query identifier of the env").DataType("string")).
Param(ws.QueryParameter("status", "query identifier of the status").DataType("string")).
Param(ws.QueryParameter("page", "query the page number").DataType("integer")).
Param(ws.QueryParameter("pageSize", "query the page size number").DataType("integer")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.ListRevisionsResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.ListRevisionsResponse{}))
ws.Route(ws.GET("/{name}/revisions/{revision}").To(c.detailApplicationRevision).
Doc("detail revision for application").
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("revision", "identifier of the application revision").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.DetailRevisionResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.DetailRevisionResponse{}))
return ws
}
@@ -695,3 +719,33 @@ func (c *applicationWebService) getApplicationStatus(req *restful.Request, res *
return
}
}
func (c *applicationWebService) listApplicationRevisions(req *restful.Request, res *restful.Response) {
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
revisions, err := c.applicationUsecase.ListRevisions(req.Request.Context(), req.PathParameter("name"), req.QueryParameter("envName"), req.QueryParameter("status"), page, pageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(revisions); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *applicationWebService) detailApplicationRevision(req *restful.Request, res *restful.Response) {
detail, err := c.applicationUsecase.DetailRevision(req.Request.Context(), req.PathParameter("name"), req.PathParameter("revision"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(detail); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
+2 -2
View File
@@ -99,8 +99,8 @@ func (w *workflowWebService) GetWebService() *restful.WebService {
Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(w.workflowCheckFilter).
Param(ws.PathParameter("page", "Query the page number.").DataType("integer")).
Param(ws.PathParameter("pageSize", "Query the page size number.").DataType("integer")).
Param(ws.QueryParameter("page", "query the page number").DataType("integer")).
Param(ws.QueryParameter("pageSize", "query the page size number").DataType("integer")).
Returns(200, "", apis.ListWorkflowRecordsResponse{}).
Writes(apis.ListWorkflowRecordsResponse{}).Do(returns200, returns500))