From adcb7bd65e6119b0b04007f58a134f36cd754b03 Mon Sep 17 00:00:00 2001 From: barnettZQG <576501057@qq.com> Date: Sat, 20 Nov 2021 19:50:36 +0800 Subject: [PATCH] Feat: support update component and query app statistics info (#2746) * Feat: change swagger config * Feat: support update component and query app statistics info * Fix: fix workflow list bug * Fix: fix test bug * Fix: fix e2e test bug * Feat: change workflow api * Fix: fix app deploy e2e test bug * Fix: change e2e test * Fix: fix workflow bug * Fix: fix deploy bug * Fix: fix selector bug * Feat: support recycle env * Fix: debug e2e * Fix: fix e2e case bug Co-authored-by: barnettZQG --- docs/apidoc/swagger.json | 1228 ++++++++++------- pkg/apiserver/model/workflow.go | 10 +- pkg/apiserver/rest/apis/v1/types.go | 67 +- pkg/apiserver/rest/usecase/application.go | 302 ++-- .../rest/usecase/application_test.go | 94 +- pkg/apiserver/rest/usecase/delivery_target.go | 3 + pkg/apiserver/rest/usecase/envbinding.go | 88 +- .../rest/usecase/testdata/ui-schema.yaml | 184 +-- pkg/apiserver/rest/usecase/workflow.go | 235 ++-- pkg/apiserver/rest/usecase/workflow_test.go | 231 +++- pkg/apiserver/rest/utils/bcode/application.go | 9 + pkg/apiserver/rest/utils/bcode/workflow.go | 6 + pkg/apiserver/rest/webservice/application.go | 239 +++- .../rest/webservice/delivery_target.go | 2 +- pkg/apiserver/rest/webservice/webservice.go | 3 +- pkg/apiserver/rest/webservice/workflow.go | 161 +-- pkg/oam/labels.go | 8 +- test/e2e-apiserver-test/application_test.go | 115 +- test/e2e-apiserver-test/suite_test.go | 2 +- 19 files changed, 1793 insertions(+), 1194 deletions(-) diff --git a/docs/apidoc/swagger.json b/docs/apidoc/swagger.json index 8283de3a9..93dc02006 100644 --- a/docs/apidoc/swagger.json +++ b/docs/apidoc/swagger.json @@ -228,6 +228,12 @@ "name": "name", "in": "path", "required": true + }, + { + "type": "string", + "description": "filter addons from given registry", + "name": "registry", + "in": "query" } ], "responses": { @@ -834,7 +840,7 @@ "tags": [ "application" ], - "summary": "detail component for application ", + "summary": "detail component for application ", "operationId": "detailComponent", "parameters": [ { @@ -857,6 +863,50 @@ } } } + }, + "put": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "update component config", + "operationId": "updateComponent", + "parameters": [ + { + "type": "string", + "description": "identifier of the application", + "name": "name", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.UpdateApplicationComponentRequest" + } + } + ], + "responses": { + "200": { + "schema": { + "$ref": "#/definitions/v1.ComponentBase" + } + }, + "400": { + "schema": { + "$ref": "#/definitions/bcode.Bcode" + } + } + } } }, "/api/v1/applications/{name}/deploy": { @@ -1448,6 +1498,44 @@ } } }, + "/api/v1/applications/{name}/statistics": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "detail one application ", + "operationId": "applicationStatistics", + "parameters": [ + { + "type": "string", + "description": "identifier of the application ", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "schema": { + "$ref": "#/definitions/v1.ApplicationStatisticsResponse" + } + }, + "400": { + "schema": { + "$ref": "#/definitions/bcode.Bcode" + } + } + } + } + }, "/api/v1/applications/{name}/template": { "post": { "consumes": [ @@ -1494,6 +1582,485 @@ } } }, + "/api/v1/applications/{name}/workflows": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "list application workflow", + "operationId": "listApplicationWorkflows", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + }, + "post": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "create application workflow", + "operationId": "createApplicationWorkflow", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.CreateWorkflowRequest" + } + }, + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "400": { + "description": "create failure", + "schema": { + "$ref": "#/definitions/bcode.Bcode" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + } + }, + "/api/v1/applications/{name}/workflows/{workflowName}": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "detail application workflow", + "operationId": "detailWorkflow", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workfloc.", + "name": "workflowName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + }, + "put": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "update application workflow config", + "operationId": "updateWorkflow", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.UpdateWorkflowRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + }, + "delete": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "deletet workflow", + "operationId": "deleteWorkflow", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + } + }, + "/api/v1/applications/{name}/workflows/{workflowName}/records": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "query application workflow execution record", + "operationId": "listWorkflowRecords", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + }, + { + "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": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + } + }, + "/api/v1/applications/{name}/workflows/{workflowName}/records/{record}": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "query application workflow execution record detail", + "operationId": "detailWorkflowRecord", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow record", + "name": "record", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/map[string]string" + } + }, + "500": { + "description": "Bummer, something went wrong" + } + } + } + }, + "/api/v1/applications/{name}/workflows/{workflowName}/records/{record}/resume": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "resume suspend workflow record", + "operationId": "resumeWorkflowRecord", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow record", + "name": "record", + "in": "path", + "required": true + } + ], + "responses": { + "200": {}, + "400": { + "schema": { + "$ref": "#/definitions/bcode.Bcode" + } + } + } + } + }, + "/api/v1/applications/{name}/workflows/{workflowName}/records/{record}/rollback": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "rollback suspend application record", + "operationId": "rollbackWorkflowRecord", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow record", + "name": "record", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the rollback revision", + "name": "rollbackVersion", + "in": "query" + } + ], + "responses": { + "200": {}, + "400": { + "schema": { + "$ref": "#/definitions/bcode.Bcode" + } + } + } + } + }, + "/api/v1/applications/{name}/workflows/{workflowName}/records/{record}/terminate": { + "get": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "application" + ], + "summary": "terminate suspend workflow record", + "operationId": "terminateWorkflowRecord", + "parameters": [ + { + "type": "string", + "description": "identifier of the application.", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow", + "name": "workflowName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the workflow record", + "name": "record", + "in": "path", + "required": true + } + ], + "responses": { + "200": {}, + "400": { + "schema": { + "$ref": "#/definitions/bcode.Bcode" + } + } + } + } + }, "/api/v1/clusters": { "get": { "consumes": [ @@ -2441,428 +3008,6 @@ } } }, - "/api/v1/workflows": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "list application workflow", - "operationId": "listApplicationWorkflows", - "parameters": [ - { - "type": "string", - "description": "identifier of the application.", - "name": "appName", - "in": "query", - "required": true - }, - { - "type": "boolean", - "description": "query based on enable status", - "name": "enable", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - }, - "post": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "create application workflow", - "operationId": "createApplicationWorkflow", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1.CreateWorkflowRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "400": { - "description": "create failure", - "schema": { - "$ref": "#/definitions/bcode.Bcode" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - } - }, - "/api/v1/workflows/{name}": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "detail application workflow", - "operationId": "detailWorkflow", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow.", - "name": "name", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - }, - "put": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "update application workflow config", - "operationId": "updateWorkflow", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1.UpdateWorkflowRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - }, - "delete": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "deletet workflow", - "operationId": "deleteWorkflow", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - } - }, - "/api/v1/workflows/{name}/records": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "query application workflow execution record", - "operationId": "listWorkflowRecords", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - }, - { - "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": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - } - }, - "/api/v1/workflows/{name}/records/{record}": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "query application workflow execution record detail", - "operationId": "detailWorkflowRecord", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "identifier of the workflow record", - "name": "record", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/map[string]string" - } - }, - "500": { - "description": "Bummer, something went wrong" - } - } - } - }, - "/api/v1/workflows/{name}/records/{record}/resume": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "resume suspend workflow record", - "operationId": "resumeWorkflowRecord", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "identifier of the workflow record", - "name": "record", - "in": "path", - "required": true - } - ], - "responses": { - "200": {}, - "400": { - "schema": { - "$ref": "#/definitions/bcode.Bcode" - } - } - } - } - }, - "/api/v1/workflows/{name}/records/{record}/rollback": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "rollback suspend application record", - "operationId": "rollbackWorkflowRecord", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "identifier of the workflow record", - "name": "record", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "identifier of the rollback revision", - "name": "rollbackVersion", - "in": "query" - } - ], - "responses": { - "200": {}, - "400": { - "schema": { - "$ref": "#/definitions/bcode.Bcode" - } - } - } - } - }, - "/api/v1/workflows/{name}/records/{record}/terminate": { - "get": { - "consumes": [ - "application/xml", - "application/json" - ], - "produces": [ - "application/json", - "application/xml" - ], - "tags": [ - "workflow" - ], - "summary": "terminate suspend workflow record", - "operationId": "terminateWorkflowRecord", - "parameters": [ - { - "type": "string", - "description": "identifier of the workflow", - "name": "name", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "identifier of the workflow record", - "name": "record", - "in": "path", - "required": true - } - ], - "responses": { - "200": {}, - "400": { - "schema": { - "$ref": "#/definitions/bcode.Bcode" - } - } - } - } - }, "/v1/namespaces/{namespace}/applications/{appname}": { "get": { "consumes": [ @@ -3072,11 +3217,11 @@ }, "common.AppRolloutStatus": { "required": [ - "rollingState", - "currentBatch", - "batchRollingState", - "upgradedReplicas", "upgradedReadyReplicas", + "rollingState", + "batchRollingState", + "currentBatch", + "upgradedReplicas", "lastTargetAppRevision" ], "properties": { @@ -3566,8 +3711,8 @@ }, "model.ApplicationComponent": { "required": [ - "createTime", "updateTime", + "createTime", "appPrimaryKey", "creator", "name", @@ -3650,8 +3795,8 @@ }, "model.ApplicationRevision": { "required": [ - "updateTime", "createTime", + "updateTime", "appPrimaryKey", "version", "status", @@ -3885,7 +4030,7 @@ "$ref": "#/definitions/types.AddonDependency" } }, - "deploy_to": { + "deployTo": { "$ref": "#/definitions/types.AddonDeployTo" }, "description": { @@ -4074,6 +4219,19 @@ } } }, + "v1.AddonDefinition": { + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "v1.AddonRegistryMeta": { "required": [ "name" @@ -4173,15 +4331,14 @@ }, "v1.ApplicationDeployResponse": { "required": [ - "name", - "version", "envName", + "triggerType", "createTime", + "version", "status", "reason", "deployUser", - "note", - "triggerType" + "note" ], "properties": { "createTime": { @@ -4194,9 +4351,6 @@ "envName": { "type": "string" }, - "name": { - "type": "string" - }, "note": { "type": "string" }, @@ -4243,7 +4397,7 @@ "properties": { "componentNum": { "type": "integer", - "format": "int32" + "format": "int64" } } }, @@ -4272,7 +4426,6 @@ "v1.ApplicationRevisionBase": { "required": [ "createTime", - "name", "version", "status", "reason", @@ -4292,9 +4445,6 @@ "envName": { "type": "string" }, - "name": { - "type": "string" - }, "note": { "type": "string" }, @@ -4312,6 +4462,32 @@ } } }, + "v1.ApplicationStatisticsResponse": { + "required": [ + "envCount", + "deliveryTargetCount", + "revisonCount", + "workflowCount" + ], + "properties": { + "deliveryTargetCount": { + "type": "integer", + "format": "int64" + }, + "envCount": { + "type": "integer", + "format": "int64" + }, + "revisonCount": { + "type": "integer", + "format": "int64" + }, + "workflowCount": { + "type": "integer", + "format": "int64" + } + } + }, "v1.ApplicationStatusResponse": { "required": [ "envName", @@ -4673,8 +4849,8 @@ }, "v1.CreateApplicationEnvRequest": { "required": [ - "name", - "targetNames" + "targetNames", + "name" ], "properties": { "alias": { @@ -5057,6 +5233,10 @@ "alias": { "type": "string" }, + "appNum": { + "type": "integer", + "format": "int64" + }, "cluster": { "$ref": "#/definitions/v1.ClusterTarget" }, @@ -5084,21 +5264,28 @@ }, "v1.DetailAddonResponse": { "required": [ - "description", - "icon", "name", "version", + "description", + "icon", "schema", - "uiSchema" + "uiSchema", + "definitions" ], "properties": { + "definitions": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.AddonDefinition" + } + }, "dependencies": { "type": "array", "items": { "$ref": "#/definitions/types.AddonDependency" } }, - "deploy_to": { + "deployTo": { "$ref": "#/definitions/types.AddonDeployTo" }, "description": { @@ -5138,18 +5325,17 @@ }, "v1.DetailApplicationResponse": { "required": [ - "alias", - "namespace", "description", "createTime", "updateTime", "icon", "name", + "alias", + "namespace", "policies", "envBindings", "status", - "resourceInfo", - "workflowStatus" + "resourceInfo" ], "properties": { "alias": { @@ -5198,30 +5384,24 @@ "updateTime": { "type": "string", "format": "date-time" - }, - "workflowStatus": { - "type": "array", - "items": { - "$ref": "#/definitions/v1.WorkflowStepStatus" - } } } }, "v1.DetailClusterResponse": { "required": [ - "provider", "apiServerURL", - "model", "name", - "description", + "labels", + "reason", "icon", + "status", + "provider", "dashboardURL", "kubeConfig", - "kubeConfigSecret", + "model", "alias", - "labels", - "status", - "reason", + "description", + "kubeConfigSecret", "resourceInfo" ], "properties": { @@ -5274,13 +5454,13 @@ }, "v1.DetailComponentResponse": { "required": [ - "updateTime", - "type", - "creator", - "alias", "createTime", + "type", + "updateTime", + "name", "appPrimaryKey", - "name" + "creator", + "alias" ], "properties": { "alias": { @@ -5375,15 +5555,19 @@ }, "v1.DetailDeliveryTargetResponse": { "required": [ - "createTime", - "updateTime", "name", - "namespace" + "updateTime", + "namespace", + "createTime" ], "properties": { "alias": { "type": "string" }, + "appNum": { + "type": "integer", + "format": "int64" + }, "cluster": { "$ref": "#/definitions/v1.ClusterTarget" }, @@ -5411,13 +5595,13 @@ }, "v1.DetailPolicyResponse": { "required": [ - "name", - "type", - "description", "creator", "properties", "createTime", - "updateTime" + "updateTime", + "name", + "type", + "description" ], "properties": { "createTime": { @@ -5447,17 +5631,17 @@ }, "v1.DetailRevisionResponse": { "required": [ + "note", + "updateTime", + "appPrimaryKey", + "version", "reason", - "deployUser", "triggerType", "workflowName", "envName", "createTime", - "version", - "note", - "updateTime", - "appPrimaryKey", - "status" + "status", + "deployUser" ], "properties": { "appPrimaryKey": { @@ -5548,14 +5732,13 @@ "v1.DetailWorkflowResponse": { "required": [ "name", + "enable", + "envName", "alias", "description", - "enable", "default", - "envName", "createTime", - "updateTime", - "workflowRecord" + "updateTime" ], "properties": { "alias": { @@ -5589,9 +5772,6 @@ "updateTime": { "type": "string", "format": "date-time" - }, - "workflowRecord": { - "$ref": "#/definitions/v1.WorkflowRecord" } } }, @@ -5653,12 +5833,16 @@ "name", "targetNames", "createTime", - "updateTime" + "updateTime", + "appDeployName" ], "properties": { "alias": { "type": "string" }, + "appDeployName": { + "type": "string" + }, "componentSelector": { "$ref": "#/definitions/v1.ComponentSelector" }, @@ -5666,6 +5850,12 @@ "type": "string", "format": "date-time" }, + "deliveryTargets": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DeliveryTargetBase" + } + }, "description": { "type": "string" }, @@ -6071,6 +6261,37 @@ } } }, + "v1.UpdateApplicationComponentRequest": { + "properties": { + "alias": { + "type": "string" + }, + "dependsOn": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/v1.UpdateApplicationComponentRequest.labels" + }, + "properties": { + "type": "string" + } + } + }, + "v1.UpdateApplicationComponentRequest.labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "v1.UpdateApplicationRequest": { "properties": { "alias": { @@ -6207,6 +6428,12 @@ "name": { "type": "string" }, + "steps": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.WorkflowStep" + } + }, "updateTime": { "type": "string", "format": "date-time" @@ -6282,25 +6509,6 @@ } } }, - "v1.WorkflowStepStatus": { - "required": [ - "name", - "status", - "takeTime" - ], - "properties": { - "name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "takeTime": { - "type": "integer", - "format": "integer" - } - } - }, "v1alpha1.CanaryMetric": { "required": [ "name" diff --git a/pkg/apiserver/model/workflow.go b/pkg/apiserver/model/workflow.go index 2827682b5..a7cd4bf26 100644 --- a/pkg/apiserver/model/workflow.go +++ b/pkg/apiserver/model/workflow.go @@ -17,6 +17,7 @@ limitations under the License. package model import ( + "fmt" "strconv" "time" @@ -51,6 +52,7 @@ type WorkflowStep struct { OrderIndex int `json:"orderIndex"` Inputs common.StepInputs `json:"inputs,omitempty"` Outputs common.StepOutputs `json:"outputs,omitempty"` + DependsOn []string `json:"dependsOn"` Properties *JSONStruct `json:"properties,omitempty"` } @@ -61,7 +63,7 @@ func (w *Workflow) TableName() string { // PrimaryKey return custom primary key func (w *Workflow) PrimaryKey() string { - return w.Name + return fmt.Sprintf("%s-%s", w.AppPrimaryKey, w.Name) } // Index return custom primary key @@ -83,7 +85,7 @@ func (w *Workflow) Index() map[string]string { // WorkflowRecord is the workflow record database model type WorkflowRecord struct { Model - WorkflowPrimaryKey string `json:"workflowPrimaryKey"` + WorkflowName string `json:"workflowName"` AppPrimaryKey string `json:"appPrimaryKey"` RevisionPrimaryKey string `json:"revisionPrimaryKey"` Name string `json:"name"` @@ -113,8 +115,8 @@ func (w *WorkflowRecord) Index() map[string]string { if w.Namespace != "" { index["namespace"] = w.Namespace } - if w.WorkflowPrimaryKey != "" { - index["workflowPrimaryKey"] = w.WorkflowPrimaryKey + if w.WorkflowName != "" { + index["workflowPrimaryKey"] = w.WorkflowName } if w.AppPrimaryKey != "" { index["appPrimaryKey"] = w.AppPrimaryKey diff --git a/pkg/apiserver/rest/apis/v1/types.go b/pkg/apiserver/rest/apis/v1/types.go index 7919a53d9..1b2cee373 100644 --- a/pkg/apiserver/rest/apis/v1/types.go +++ b/pkg/apiserver/rest/apis/v1/types.go @@ -39,6 +39,8 @@ var ( CtxKeyDeliveryTarget = "delivery-target" // CtxKeyApplicationEnvBinding request context key of env binding CtxKeyApplicationEnvBinding = "envbinding-policy" + // CtxKeyApplicationComponent request context key of component + CtxKeyApplicationComponent = "component" ) // AddonPhase defines the phase of an addon @@ -283,6 +285,14 @@ type ApplicationStatusResponse struct { Status *common.AppStatus `json:"status"` } +// ApplicationStatisticsResponse application statistics response body +type ApplicationStatisticsResponse struct { + EnvCount int64 `json:"envCount"` + DeliveryTargetCount int64 `json:"deliveryTargetCount"` + RevisonCount int64 `json:"revisonCount"` + WorkflowCount int64 `json:"workflowCount"` +} + // CreateApplicationRequest create application request body type CreateApplicationRequest struct { Name string `json:"name" validate:"checkname"` @@ -315,13 +325,15 @@ type EnvBinding struct { // 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"` + 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"` + Targets []DeliveryTargetBase `json:"deliveryTargets,omitempty"` + ComponentSelector *ComponentSelector `json:"componentSelector" optional:"true"` + CreateTime time.Time `json:"createTime"` + UpdateTime time.Time `json:"updateTime"` + AppDeployName string `json:"appDeployName"` } // DetailEnvBindingResponse defines the response of env-binding details @@ -344,11 +356,10 @@ type ComponentSelector struct { // DetailApplicationResponse application detail type DetailApplicationResponse struct { ApplicationBase - Policies []string `json:"policies"` - EnvBindings []string `json:"envBindings"` - Status string `json:"status"` - ResourceInfo ApplicationResourceInfo `json:"resourceInfo"` - WorkflowStatus []WorkflowStepStatus `json:"workflowStatus"` + Policies []string `json:"policies"` + EnvBindings []string `json:"envBindings"` + Status string `json:"status"` + ResourceInfo ApplicationResourceInfo `json:"resourceInfo"` } // WorkflowStepStatus workflow step status model @@ -360,7 +371,7 @@ type WorkflowStepStatus struct { // ApplicationResourceInfo application-level resource consumption statistics type ApplicationResourceInfo struct { - ComponentNum int `json:"componentNum"` + ComponentNum int64 `json:"componentNum"` // Others, such as: Memory、CPU、GPU、Storage } @@ -398,6 +409,16 @@ type CreateComponentRequest struct { Traits []*CreateApplicationTraitRequest `json:"traits,omitempty" optional:"true"` } +// UpdateApplicationComponentRequest update component request body +type UpdateApplicationComponentRequest struct { + Alias *string `json:"alias" validate:"checkalias" optional:"true"` + Description *string `json:"description" optional:"true"` + Icon *string `json:"icon" optional:"true"` + Labels *map[string]string `json:"labels,omitempty"` + Properties *string `json:"properties,omitempty"` + DependsOn *[]string `json:"dependsOn" optional:"true"` +} + // DetailComponentResponse detail component response body type DetailComponentResponse struct { model.ApplicationComponent @@ -567,8 +588,6 @@ type WorkflowStep struct { // DetailWorkflowResponse detail workflow response type DetailWorkflowResponse struct { WorkflowBase - Steps []WorkflowStep `json:"steps,omitempty"` - LastRecord *WorkflowRecord `json:"workflowRecord"` } // ListWorkflowResponse list application workflows @@ -578,14 +597,15 @@ type ListWorkflowResponse struct { // WorkflowBase workflow base model type WorkflowBase struct { - Name string `json:"name"` - Alias string `json:"alias"` - 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"` + Name string `json:"name"` + Alias string `json:"alias"` + 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"` + Steps []WorkflowStep `json:"steps,omitempty"` } // ListWorkflowRecordsResponse list workflow execution record @@ -722,6 +742,7 @@ type DeliveryTargetBase struct { Variable map[string]interface{} `json:"variable,omitempty"` CreateTime time.Time `json:"createTime"` UpdateTime time.Time `json:"updateTime"` + AppNum int64 `json:"appNum,omitempty"` } // ApplicationRevisionBase application revision base spec diff --git a/pkg/apiserver/rest/usecase/application.go b/pkg/apiserver/rest/usecase/application.go index d55405839..b6644edfc 100644 --- a/pkg/apiserver/rest/usecase/application.go +++ b/pkg/apiserver/rest/usecase/application.go @@ -18,7 +18,6 @@ package usecase import ( "context" - "encoding/json" "errors" "fmt" "sort" @@ -28,6 +27,8 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -68,10 +69,12 @@ type ApplicationUsecase interface { UpdateApplication(context.Context, *model.Application, apisv1.UpdateApplicationRequest) (*apisv1.ApplicationBase, error) DeleteApplication(ctx context.Context, app *model.Application) error Deploy(ctx context.Context, app *model.Application, req apisv1.ApplicationDeployRequest) (*apisv1.ApplicationDeployResponse, error) + GetApplicationComponent(ctx context.Context, app *model.Application, componentName string) (*model.ApplicationComponent, error) ListComponents(ctx context.Context, app *model.Application, op apisv1.ListApplicationComponentOptions) ([]*apisv1.ComponentBase, error) AddComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest) (*apisv1.ComponentBase, error) DetailComponent(ctx context.Context, app *model.Application, componentName string) (*apisv1.DetailComponentResponse, error) DeleteComponent(ctx context.Context, app *model.Application, componentName string) error + UpdateComponent(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.UpdateApplicationComponentRequest) (*apisv1.ComponentBase, error) ListPolicies(ctx context.Context, app *model.Application) ([]*apisv1.PolicyBase, error) AddPolicy(ctx context.Context, app *model.Application, policy apisv1.CreatePolicyRequest) (*apisv1.PolicyBase, error) DetailPolicy(ctx context.Context, app *model.Application, policyName string) (*apisv1.DetailPolicyResponse, error) @@ -81,7 +84,8 @@ type ApplicationUsecase interface { 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, revisionVersion string) (*apisv1.DetailRevisionResponse, error) + DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error) + Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error) } type applicationUsecaseImpl struct { @@ -163,7 +167,7 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod if err != nil { return nil, err } - components, err := c.ListComponents(ctx, app, apisv1.ListApplicationComponentOptions{}) + componentNum, err := c.ds.Count(ctx, &model.ApplicationComponent{AppPrimaryKey: app.PrimaryKey()}, &datastore.FilterOptions{}) if err != nil { return nil, err } @@ -184,9 +188,8 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod Policies: policyNames, EnvBindings: envBindingNames, ResourceInfo: apisv1.ApplicationResourceInfo{ - ComponentNum: len(components), + ComponentNum: componentNum, }, - WorkflowStatus: []apisv1.WorkflowStepStatus{}, } return detail, nil } @@ -194,7 +197,7 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod // GetApplicationStatus get application status from controller cluster func (c *applicationUsecaseImpl) GetApplicationStatus(ctx context.Context, appmodel *model.Application, envName string) (*common.AppStatus, error) { var app v1beta1.Application - err := c.kubeClient.Get(ctx, types.NamespacedName{Namespace: appmodel.Namespace, Name: converAppName(appmodel, envName)}, &app) + err := c.kubeClient.Get(ctx, types.NamespacedName{Namespace: appmodel.Namespace, Name: converAppName(appmodel.Name, envName)}, &app) if err != nil { if apierrors.IsNotFound(err) { return nil, nil @@ -204,6 +207,27 @@ func (c *applicationUsecaseImpl) GetApplicationStatus(ctx context.Context, appmo return &app.Status, nil } +// GetApplicationCR get application cr in cluster +func (c *applicationUsecaseImpl) GetApplicationCR(ctx context.Context, appmodel *model.Application) (*v1beta1.ApplicationList, error) { + var apps v1beta1.ApplicationList + selector := labels.NewSelector() + re, err := labels.NewRequirement(oam.AnnotationAppName, selection.Equals, []string{appmodel.Name}) + if err != nil { + return nil, err + } + selector = selector.Add(*re) + err = c.kubeClient.List(ctx, &apps, &client.ListOptions{ + LabelSelector: selector, + }) + if err != nil { + if apierrors.IsNotFound(err) { + return &apps, nil + } + return nil, err + } + return &apps, nil +} + // PublishApplicationTemplate publish app template func (c *applicationUsecaseImpl) PublishApplicationTemplate(ctx context.Context, app *model.Application) (*apisv1.ApplicationTemplateBase, error) { //TODO: @@ -247,13 +271,6 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis return nil, err } } - if oamApp.Spec.Workflow != nil && len(oamApp.Spec.Workflow.Steps) > 0 { - 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 @@ -301,46 +318,12 @@ func (c *applicationUsecaseImpl) genPolicyByEnv(ctx context.Context, app *model. } properties, err := model.NewJSONStructByStruct(envBindingSpec) if err != nil { - return appPolicy, err + return appPolicy, bcode.ErrInvalidProperties } 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 { @@ -495,7 +478,6 @@ func (c *applicationUsecaseImpl) converPolicyModelToBase(policy *model.Applicati func (c *applicationUsecaseImpl) saveApplicationPolicy(ctx context.Context, app *model.Application, policys []v1beta1.AppPolicy) error { var policyModels []datastore.Entity - var envbindingPolicy *model.ApplicationPolicy for _, policy := range policys { properties, err := model.NewJSONStruct(policy.Properties) if err != nil { @@ -510,26 +492,6 @@ func (c *applicationUsecaseImpl) saveApplicationPolicy(ctx context.Context, app } if policy.Type != string(EnvBindingPolicy) { policyModels = append(policyModels, appPolicy) - } else { - envbindingPolicy = appPolicy - } - } - // If multiple configurations are configured, enable only the last one. - if envbindingPolicy != nil { - envbindingPolicy.Name = EnvBindingPolicyDefaultName - policyModels = append(policyModels, envbindingPolicy) - var envBindingSpec v1alpha1.EnvBindingSpec - if err := json.Unmarshal([]byte(envbindingPolicy.Properties.JSON()), &envBindingSpec); err != nil { - return fmt.Errorf("unmarshal env binding policy failure %w", err) - } - for _, env := range envBindingSpec.Envs { - envBind := &model.EnvBinding{ - Name: env.Name, - Description: "", - } - if env.Selector != nil { - envBind.ComponentSelector = (*model.ComponentSelector)(env.Selector) - } } } return c.ds.BatchAdd(ctx, policyModels) @@ -578,26 +540,30 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat return nil, err } configByte, _ := yaml.Marshal(oamApp) + + workflow, err := c.workflowUsecase.GetWorkflow(ctx, app, oamApp.Annotations[oam.AnnotationWorkflowName]) + if err != nil { + return nil, err + } + // step2: check and create deploy event if !req.Force { var lastVersion = model.ApplicationRevision{ AppPrimaryKey: app.PrimaryKey(), + EnvName: workflow.EnvName, } - list, err := c.ds.List(ctx, &lastVersion, &datastore.ListOptions{PageSize: 1, Page: 1}) + list, err := c.ds.List(ctx, &lastVersion, &datastore.ListOptions{ + PageSize: 1, Page: 1, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}}) if err != nil && !errors.Is(err, datastore.ErrRecordNotExist) { log.Logger.Errorf("query app latest revision failure %s", err.Error()) return nil, bcode.ErrDeployConflict } if len(list) > 0 && list[0].(*model.ApplicationRevision).Status != model.RevisionStatusComplete { + log.Logger.Warnf("last app revision can not complete %s/%s", list[0].(*model.ApplicationRevision).AppPrimaryKey, list[0].(*model.ApplicationRevision).Version) return nil, bcode.ErrDeployConflict } } - 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, @@ -615,7 +581,7 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat return nil, err } // step3: create workflow record - if err := c.workflowUsecase.CreateWorkflowRecord(ctx, oamApp); err != nil { + if err := c.workflowUsecase.CreateWorkflowRecord(ctx, app, oamApp, workflow); err != nil { return nil, err } // step4: check and create namespace @@ -657,19 +623,46 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat } func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, version string) (*v1beta1.Application, error) { + // Priority 1 uses the requested workflow as release . + // Priority 2 uses the default workflow as release . + var workflow *model.Workflow + var err error + if reqWorkflowName != "" { + workflow, err = c.workflowUsecase.GetWorkflow(ctx, appModel, reqWorkflowName) + if err != nil { + return nil, err + } + } else { + workflow, err = c.workflowUsecase.GetApplicationDefaultWorkflow(ctx, appModel) + if err != nil && !errors.Is(err, bcode.ErrWorkflowNoDefault) { + return nil, err + } + } + if workflow == nil || workflow.EnvName == "" { + return nil, bcode.ErrWorkflowNotExist + } + + labels := make(map[string]string) + for key, value := range appModel.Labels { + labels[key] = value + } + labels[oam.AnnotationAppName] = appModel.Name + var app = &v1beta1.Application{ TypeMeta: metav1.TypeMeta{ Kind: "Application", APIVersion: "core.oam.dev/v1beta1", }, ObjectMeta: metav1.ObjectMeta{ - Name: appModel.Name, + Name: converAppName(appModel.Name, workflow.EnvName), Namespace: appModel.Namespace, - Labels: appModel.Labels, + Labels: labels, Annotations: map[string]string{ oam.AnnotationDeployVersion: version, // publish version is the identifier of workflow record oam.AnnotationPublishVersion: utils.GenerateVersion(reqWorkflowName), + oam.AnnotationAppName: appModel.Name, + oam.AnnotationAppAlias: appModel.Alias, }, }, } @@ -705,7 +698,7 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo traits = append(traits, aTrait) } bc := common.ApplicationComponent{ - Name: component.Name, + Name: converComponentName(component.Name, workflow.EnvName), Type: component.Type, ExternalRevision: component.ExternalRevision, DependsOn: component.DependsOn, @@ -732,48 +725,29 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo } app.Spec.Policies = append(app.Spec.Policies, apolicy) } - - // Priority 1 uses the requested workflow as release . - // Priority 2 uses the default workflow as release . - var workflow *model.Workflow - if reqWorkflowName != "" { - workflow, err = c.workflowUsecase.GetWorkflow(ctx, reqWorkflowName) + if workflow.EnvName != "" { + envPolicy, err := c.genPolicyByEnv(ctx, appModel, workflow.EnvName) 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) { - return nil, err - } + app.Spec.Policies = append(app.Spec.Policies, envPolicy) } - - if workflow != nil { - app.Annotations[oam.AnnotationWorkflowName] = workflow.Name - var steps []v1beta1.WorkflowStep - for _, step := range workflow.Steps { - var wstep = v1beta1.WorkflowStep{ - Name: step.Name, - Type: step.Type, - Inputs: step.Inputs, - Outputs: step.Outputs, - } - if step.Properties != nil { - wstep.Properties = step.Properties.RawExtension() - } - steps = append(steps, wstep) + app.Annotations[oam.AnnotationWorkflowName] = workflow.Name + var steps []v1beta1.WorkflowStep + for _, step := range workflow.Steps { + var wstep = v1beta1.WorkflowStep{ + Name: step.Name, + Type: step.Type, + Inputs: step.Inputs, + Outputs: step.Outputs, } - app.Spec.Workflow = &v1beta1.Workflow{ - Steps: steps, + if step.Properties != nil { + wstep.Properties = step.Properties.RawExtension() } + steps = append(steps, wstep) + } + app.Spec.Workflow = &v1beta1.Workflow{ + Steps: steps, } return app, nil @@ -796,7 +770,13 @@ func (c *applicationUsecaseImpl) converAppModelToBase(app *model.Application) *a // DeleteApplication delete application func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *model.Application) error { // TODO: check app can be deleted - + crs, err := c.GetApplicationCR(ctx, app) + if err != nil { + return err + } + if len(crs.Items) > 0 { + return bcode.ErrApplicationRefusedDelete + } // query all components to deleted components, err := c.ListComponents(ctx, app, apisv1.ListApplicationComponentOptions{}) if err != nil { @@ -809,7 +789,7 @@ func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *mod } // delete workflow - if err := c.workflowUsecase.DeleteWorkflow(ctx, app.Name); err != nil && !errors.Is(err, bcode.ErrWorkflowNotExist) { + if err := c.workflowUsecase.DeleteWorkflowByApp(ctx, app); err != nil && !errors.Is(err, bcode.ErrWorkflowNotExist) { log.Logger.Errorf("delete workflow %s failure %s", app.Name, err.Error()) } @@ -834,6 +814,50 @@ func (c *applicationUsecaseImpl) DeleteApplication(ctx context.Context, app *mod return c.ds.Delete(ctx, app) } +func (c *applicationUsecaseImpl) GetApplicationComponent(ctx context.Context, app *model.Application, componentName string) (*model.ApplicationComponent, error) { + var component = model.ApplicationComponent{ + AppPrimaryKey: app.PrimaryKey(), + Name: componentName, + } + err := c.ds.Get(ctx, &component) + if err != nil { + if errors.Is(err, datastore.ErrRecordNotExist) { + return nil, bcode.ErrApplicationComponetNotExist + } + return nil, err + } + return &component, nil +} + +func (c *applicationUsecaseImpl) UpdateComponent(ctx context.Context, app *model.Application, component *model.ApplicationComponent, req apisv1.UpdateApplicationComponentRequest) (*apisv1.ComponentBase, error) { + if req.Alias != nil { + component.Alias = *req.Alias + } + if req.Description != nil { + component.Description = *req.Description + } + if req.DependsOn != nil { + component.DependsOn = *req.DependsOn + } + if req.Icon != nil { + component.Icon = *req.Icon + } + if req.Labels != nil { + component.Labels = *req.Labels + } + if req.Properties != nil { + properties, err := model.NewJSONStructByString(*req.Properties) + if err != nil { + return nil, bcode.ErrInvalidProperties + } + component.Properties = properties + } + if err := c.ds.Put(ctx, component); err != nil { + return nil, err + } + return converComponentModelToBase(component), nil +} + func (c *applicationUsecaseImpl) AddComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest) (*apisv1.ComponentBase, error) { componentModel := model.ApplicationComponent{ AppPrimaryKey: app.PrimaryKey(), @@ -859,6 +883,13 @@ func (c *applicationUsecaseImpl) AddComponent(ctx context.Context, app *model.Ap log.Logger.Warnf("add component for app %s failure %s", app.PrimaryKey(), err.Error()) return nil, err } + return converComponentModelToBase(&componentModel), nil +} + +func converComponentModelToBase(componentModel *model.ApplicationComponent) *apisv1.ComponentBase { + if componentModel == nil { + return nil + } return &apisv1.ComponentBase{ Name: componentModel.Name, Description: componentModel.Description, @@ -869,7 +900,7 @@ func (c *applicationUsecaseImpl) AddComponent(ctx context.Context, app *model.Ap Creator: componentModel.Creator, CreateTime: componentModel.CreateTime, UpdateTime: componentModel.UpdateTime, - }, nil + } } func (c *applicationUsecaseImpl) DeleteComponent(ctx context.Context, app *model.Application, componentName string) error { @@ -1096,6 +1127,29 @@ func (c *applicationUsecaseImpl) DetailRevision(ctx context.Context, appName, re }, nil } +func (c *applicationUsecaseImpl) Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error) { + var targetMap = make(map[string]int) + envbinding, err := c.envBindingUsecase.GetEnvBindings(ctx, app) + if err != nil { + log.Logger.Errorf("query app envbinding failure %s", err.Error()) + } + for _, env := range envbinding { + for _, target := range env.TargetNames { + targetMap[target]++ + } + } + count, err := c.ds.Count(ctx, &model.ApplicationRevision{AppPrimaryKey: app.PrimaryKey()}, &datastore.FilterOptions{}) + if err != nil { + return nil, err + } + return &apisv1.ApplicationStatisticsResponse{ + EnvCount: int64(len(envbinding)), + DeliveryTargetCount: int64(len(targetMap)), + RevisonCount: count, + WorkflowCount: c.workflowUsecase.CountWorkflow(ctx, app), + }, nil +} + func createTargetClusterEnv(envBind apisv1.EnvBindingBase, target *model.DeliveryTarget) v1alpha1.EnvConfig { placement := v1alpha1.EnvPlacement{} var componentSelector *v1alpha1.EnvSelector @@ -1115,18 +1169,18 @@ func createTargetClusterEnv(envBind apisv1.EnvBindingBase, target *model.Deliver } } -func converAppName(app *model.Application, envName string) string { - return fmt.Sprintf("%s-%s", app.Name, envName) +func converAppName(appModelName, envName string) string { + return fmt.Sprintf("%s-%s", appModelName, envName) +} + +func converComponentName(componentModelName, envName string) string { + return fmt.Sprintf("%s-%s", componentModelName, 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 } diff --git a/pkg/apiserver/rest/usecase/application_test.go b/pkg/apiserver/rest/usecase/application_test.go index f0e5b94b8..3d3d38d5e 100644 --- a/pkg/apiserver/rest/usecase/application_test.go +++ b/pkg/apiserver/rest/usecase/application_test.go @@ -28,7 +28,6 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/apis/core.oam.dev/common" @@ -158,9 +157,8 @@ var _ = Describe("Test application usecase function", func() { }) It("Test ListApplications function", func() { - apps, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicatioOptions{}) + _, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicatioOptions{}) Expect(err).Should(BeNil()) - Expect(cmp.Diff(len(apps), 3)).Should(BeEmpty()) }) It("Test DetailApplication function", func() { @@ -170,29 +168,8 @@ var _ = Describe("Test application usecase function", func() { detail, err := appUsecase.DetailApplication(context.TODO(), appModel) Expect(err).Should(BeNil()) - Expect(cmp.Diff(detail.ResourceInfo.ComponentNum, 2)).Should(BeEmpty()) - Expect(cmp.Diff(len(detail.Policies), 1)).Should(BeEmpty()) - }) - - It("Test GetWorkflow 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 = workflowUsecase.GetWorkflow(context.TODO(), "test-app-sadasd") - Expect(err).Should(BeNil()) - }) - - It("Test ListPolicies 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()) - - policies, err := appUsecase.ListPolicies(context.TODO(), appModel) - Expect(err).Should(BeNil()) - Expect(cmp.Diff(len(policies), 1)).Should(BeEmpty()) - Expect(cmp.Diff(policies[0].Type, "env-binding")).Should(BeEmpty()) - Expect((*policies[0].Properties)["envs"]).ShouldNot(BeEmpty()) + Expect(cmp.Diff(detail.ResourceInfo.ComponentNum, int64(2))).Should(BeEmpty()) + Expect(cmp.Diff(len(detail.Policies), 0)).Should(BeEmpty()) }) It("Test ListComponents function", func() { @@ -233,17 +210,6 @@ var _ = Describe("Test application usecase function", func() { Expect(cmp.Diff(strings.Contains((*detail.Properties)["image"].(string), "crccheck/hello-world"), true)).Should(BeEmpty()) }) - It("Test DetailPolicy 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()) - - detail, err := appUsecase.DetailPolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName) - Expect(err).Should(BeNil()) - Expect(cmp.Diff(detail.Type, "env-binding")).Should(BeEmpty()) - Expect((*detail.Properties)["envs"]).ShouldNot(BeEmpty()) - }) - It("Test AddComponent function", func() { appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd") Expect(err).Should(BeNil()) @@ -279,23 +245,34 @@ var _ = Describe("Test application usecase function", func() { Name: EnvBindingPolicyDefaultName, Description: "this is a test2 policy", Type: "env-binding", - Properties: ``, - }) - Expect(cmp.Equal(err, bcode.ErrApplicationPolicyExist, cmpopts.EquateErrors())).Should(BeTrue()) - _, err = appUsecase.AddPolicy(context.TODO(), appModel, v1.CreatePolicyRequest{ - Name: "env-binding-2", - Description: "this is a test2 policy", - Type: "env-binding", Properties: `{"envs":{ "name": "test", "placement":{"namespaceSelector":{ "name": "TEST_NAMESPACE"}}, "selector":{ "components": ["data-worker"]}}}`, }) Expect(err).Should(BeNil()) + + _, err = appUsecase.AddPolicy(context.TODO(), appModel, v1.CreatePolicyRequest{ + Name: EnvBindingPolicyDefaultName, + Description: "this is a test2 policy", + Type: "env-binding", + Properties: ``, + }) + Expect(cmp.Equal(err, bcode.ErrApplicationPolicyExist, cmpopts.EquateErrors())).Should(BeTrue()) + }) + + It("Test ListPolicies 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()) + + policies, err := appUsecase.ListPolicies(context.TODO(), appModel) + Expect(err).Should(BeNil()) + Expect(cmp.Diff(len(policies), 1)).Should(BeEmpty()) }) It("Test DetailPolicy 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()) - detail, err := appUsecase.DetailPolicy(context.TODO(), appModel, "env-binding-2") + detail, err := appUsecase.DetailPolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName) Expect(err).Should(BeNil()) Expect(detail.Properties).ShouldNot(BeNil()) Expect((*detail.Properties)["envs"]).ShouldNot(BeEmpty()) @@ -305,7 +282,7 @@ var _ = Describe("Test application usecase 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()) - base, err := appUsecase.UpdatePolicy(context.TODO(), appModel, "env-binding-2", v1.UpdatePolicyRequest{ + base, err := appUsecase.UpdatePolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName, v1.UpdatePolicyRequest{ Type: "env-binding", Properties: `{"envs":{}}`, }) @@ -317,7 +294,7 @@ var _ = Describe("Test application usecase 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.DeletePolicy(context.TODO(), appModel, "env-binding-2") + err = appUsecase.DeletePolicy(context.TODO(), appModel, EnvBindingPolicyDefaultName) Expect(err).Should(BeNil()) }) @@ -402,24 +379,6 @@ var _ = Describe("Test application usecase function", func() { Expect(err).Should(BeNil()) }) - It("Test Deploy Application 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()) - res, err := appUsecase.Deploy(context.TODO(), appModel, v1.ApplicationDeployRequest{ - Note: "unit test deploy", - TriggerType: "api", - }) - Expect(err).Should(BeNil()) - Expect(cmp.Diff(res.Status, model.RevisionStatusRunning)).Should(BeEmpty()) - - var oam v1beta1.Application - err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: appModel.Name, Namespace: appModel.Namespace}, &oam) - Expect(err).Should(BeNil()) - Expect(cmp.Diff(len(oam.Spec.Components), 2)).Should(BeEmpty()) - Expect(cmp.Diff(len(oam.Spec.Policies), 1)).Should(BeEmpty()) - }) - It("Test DeleteApplication function", func() { appModel, err := appUsecase.GetApplication(context.TODO(), "test-app-sadasd") Expect(err).Should(BeNil()) @@ -478,15 +437,16 @@ var _ = Describe("Test application usecase function", func() { }) }) -func createTestSuspendApp(ctx context.Context, appName, revisionVersion, wfName, recordName string, kubeClient client.Client) (*v1beta1.Application, error) { +func createTestSuspendApp(ctx context.Context, appName, envName, revisionVersion, wfName, recordName string, kubeClient client.Client) (*v1beta1.Application, error) { testapp := &v1beta1.Application{ ObjectMeta: metav1.ObjectMeta{ - Name: appName, + Name: converAppName(appName, envName), Namespace: "default", Annotations: map[string]string{ oam.AnnotationDeployVersion: revisionVersion, oam.AnnotationWorkflowName: wfName, oam.AnnotationPublishVersion: recordName, + oam.AnnotationAppName: appName, }, }, Spec: v1beta1.ApplicationSpec{ diff --git a/pkg/apiserver/rest/usecase/delivery_target.go b/pkg/apiserver/rest/usecase/delivery_target.go index a5363dc3c..cbb25dd59 100644 --- a/pkg/apiserver/rest/usecase/delivery_target.go +++ b/pkg/apiserver/rest/usecase/delivery_target.go @@ -154,6 +154,8 @@ func convertCreateReqToDeliveryTargetModel(req apisv1.CreateDeliveryTargetReques } func convertFromDeliveryTargetModel(deliveryTarget *model.DeliveryTarget) *apisv1.DeliveryTargetBase { + var appNum int64 = 0 + // TODO: query app num in target return &apisv1.DeliveryTargetBase{ Name: deliveryTarget.Name, Namespace: deliveryTarget.Namespace, @@ -163,5 +165,6 @@ func convertFromDeliveryTargetModel(deliveryTarget *model.DeliveryTarget) *apisv Variable: deliveryTarget.Variable, CreateTime: deliveryTarget.CreateTime, UpdateTime: deliveryTarget.UpdateTime, + AppNum: appNum, } } diff --git a/pkg/apiserver/rest/usecase/envbinding.go b/pkg/apiserver/rest/usecase/envbinding.go index 84000bd48..f046212c0 100644 --- a/pkg/apiserver/rest/usecase/envbinding.go +++ b/pkg/apiserver/rest/usecase/envbinding.go @@ -19,16 +19,21 @@ package usecase import ( "context" "errors" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" + "github.com/oam-dev/kubevela/pkg/apiserver/clients" + "github.com/oam-dev/kubevela/pkg/apiserver/datastore" "github.com/oam-dev/kubevela/pkg/apiserver/log" + "github.com/oam-dev/kubevela/pkg/apiserver/model" + 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/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 @@ -41,19 +46,26 @@ type EnvBindingUsecase interface { 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) + DetailEnvBinding(ctx context.Context, app *model.Application, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error) + ApplicationEnvRecycle(ctx context.Context, appModel *model.Application, envBinding *model.EnvBinding) error } type envBindingUsecaseImpl struct { ds datastore.DataStore workflowUsecase WorkflowUsecase + kubeClient client.Client } // NewEnvBindingUsecase new envBinding usecase func NewEnvBindingUsecase(ds datastore.DataStore, workflowUsecase WorkflowUsecase) EnvBindingUsecase { + kubecli, err := clients.GetKubeClient() + if err != nil { + log.Logger.Fatalf("get kubeclient failure %s", err.Error()) + } return &envBindingUsecaseImpl{ ds: ds, workflowUsecase: workflowUsecase, + kubeClient: kubecli, } } @@ -65,10 +77,17 @@ func (e *envBindingUsecaseImpl) GetEnvBindings(ctx context.Context, app *model.A if err != nil { return nil, bcode.ErrEnvBindingsNotExist } + deliveryTarget := model.DeliveryTarget{ + Namespace: app.Namespace, + } + deliveryTargets, err := e.ds.List(ctx, &deliveryTarget, &datastore.ListOptions{}) + if err != nil { + return nil, err + } var list []*apisv1.EnvBindingBase for _, ebd := range envBindings { eb := ebd.(*model.EnvBinding) - list = append(list, convertEnvbindingModelToBase(eb)) + list = append(list, convertEnvbindingModelToBase(app, eb, deliveryTargets)) } return list, nil } @@ -81,7 +100,7 @@ func (e *envBindingUsecaseImpl) GetEnvBinding(ctx context.Context, app *model.Ap } return nil, err } - return e.DetailEnvBinding(ctx, envBinding) + return e.DetailEnvBinding(ctx, app, envBinding) } func (e *envBindingUsecaseImpl) CheckAppEnvBindingsContainTarget(ctx context.Context, app *model.Application, targetName string) (bool, error) { @@ -165,18 +184,23 @@ func (e *envBindingUsecaseImpl) UpdateEnvBinding(ctx context.Context, app *model if err := e.ds.Put(ctx, envBindingModel); err != nil { return nil, err } - return e.DetailEnvBinding(ctx, envBindingModel) + return e.DetailEnvBinding(ctx, app, envBindingModel) } -func (e *envBindingUsecaseImpl) DeleteEnvBinding(ctx context.Context, app *model.Application, envName string) error { - envBinding, err := e.getBindingByEnv(ctx, app, envName) +func (e *envBindingUsecaseImpl) DeleteEnvBinding(ctx context.Context, appModel *model.Application, envName string) error { + envBinding, err := e.getBindingByEnv(ctx, appModel, 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 { + var app v1beta1.Application + err = e.kubeClient.Get(ctx, types.NamespacedName{Namespace: app.Namespace, Name: converAppName(appModel.Name, envBinding.Name)}, &app) + if err != nil && !apierrors.IsNotFound(err) { + return bcode.ErrApplicationRefusedDelete + } + if err := e.ds.Delete(ctx, &model.EnvBinding{AppPrimaryKey: appModel.PrimaryKey(), Name: envBinding.Name}); err != nil { return err } return nil @@ -230,7 +254,8 @@ func (e *envBindingUsecaseImpl) createEnvWorkflow(ctx context.Context, app *mode } _, err := e.workflowUsecase.CreateWorkflow(ctx, app, apisv1.CreateWorkflowRequest{ AppName: app.PrimaryKey(), - Name: genWorkflowName(app, env.Name), + Name: env.Name, + Alias: fmt.Sprintf("%s env workflow", env.Alias), Description: "Created automatically by envbinding.", EnvName: env.Name, Steps: steps, @@ -242,12 +267,31 @@ func (e *envBindingUsecaseImpl) createEnvWorkflow(ctx context.Context, app *mode return nil } -func (e *envBindingUsecaseImpl) DetailEnvBinding(ctx context.Context, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error) { +func (e *envBindingUsecaseImpl) DetailEnvBinding(ctx context.Context, app *model.Application, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error) { + deliveryTarget := model.DeliveryTarget{ + Namespace: app.Namespace, + } + deliveryTargets, err := e.ds.List(ctx, &deliveryTarget, &datastore.ListOptions{}) + if err != nil { + return nil, err + } return &apisv1.DetailEnvBindingResponse{ - EnvBindingBase: *convertEnvbindingModelToBase(envBinding), + EnvBindingBase: *convertEnvbindingModelToBase(app, envBinding, deliveryTargets), }, nil } +func (e *envBindingUsecaseImpl) ApplicationEnvRecycle(ctx context.Context, appModel *model.Application, envBinding *model.EnvBinding) error { + var app v1beta1.Application + err := e.kubeClient.Get(ctx, types.NamespacedName{Namespace: app.Namespace, Name: converAppName(appModel.Name, envBinding.Name)}, &app) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + return e.kubeClient.Delete(ctx, &app) +} + func convertCreateReqToEnvBindingModel(app *model.Application, req apisv1.CreateApplicationEnvRequest) model.EnvBinding { envBinding := model.EnvBinding{ AppPrimaryKey: app.Name, @@ -259,15 +303,29 @@ func convertCreateReqToEnvBindingModel(app *model.Application, req apisv1.Create return envBinding } -func convertEnvbindingModelToBase(envBinding *model.EnvBinding) *apisv1.EnvBindingBase { +func convertEnvbindingModelToBase(app *model.Application, envBinding *model.EnvBinding, deliveryTargets []datastore.Entity) *apisv1.EnvBindingBase { + var dtMap = make(map[string]*model.DeliveryTarget, len(deliveryTargets)) + for _, dte := range deliveryTargets { + dt := dte.(*model.DeliveryTarget) + dtMap[dt.Name] = dt + } + var targets []apisv1.DeliveryTargetBase + for _, targetName := range envBinding.TargetNames { + dt := dtMap[targetName] + if dt != nil { + targets = append(targets, *convertFromDeliveryTargetModel(dt)) + } + } ebb := &apisv1.EnvBindingBase{ Name: envBinding.Name, Alias: envBinding.Alias, Description: envBinding.Description, TargetNames: envBinding.TargetNames, + Targets: targets, ComponentSelector: (*apisv1.ComponentSelector)(envBinding.ComponentSelector), CreateTime: envBinding.CreateTime, UpdateTime: envBinding.UpdateTime, + AppDeployName: converAppName(app.Name, envBinding.Name), } return ebb } diff --git a/pkg/apiserver/rest/usecase/testdata/ui-schema.yaml b/pkg/apiserver/rest/usecase/testdata/ui-schema.yaml index 05ce5bba3..650b814e0 100755 --- a/pkg/apiserver/rest/usecase/testdata/ui-schema.yaml +++ b/pkg/apiserver/rest/usecase/testdata/ui-schema.yaml @@ -99,20 +99,6 @@ label: 持久化存储 sort: 11 subParameters: - - description: "" - jsonKey: mountPath - label: MountPath - sort: 100 - uiType: Input - validate: - required: true - - description: "" - jsonKey: name - label: Name - sort: 100 - uiType: Input - validate: - required: true - description: 'Specify volume type, options: "pvc","configMap","secret","emptyDir"' jsonKey: type label: Type @@ -129,6 +115,20 @@ - label: EmptyDir value: emptyDir required: true + - description: "" + jsonKey: mountPath + label: MountPath + sort: 100 + uiType: Input + validate: + required: true + - description: "" + jsonKey: name + label: Name + sort: 100 + uiType: Input + validate: + required: true uiType: Structs validate: {} - description: Instructions for assessing whether the container is in a suitable state @@ -137,6 +137,24 @@ label: ReadinessProbe检测 sort: 13 subParameters: + - 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 + and the httpGet attribute. + jsonKey: tcpSocket + label: TcpSocket + sort: 100 + subParameters: + - description: The TCP socket within the container that should be probed to assess + container health. + jsonKey: port + label: Port + sort: 100 + uiType: Number + validate: + required: true + uiType: KV + validate: {} - description: Number of seconds after which the probe times out. jsonKey: timeoutSeconds label: TimeoutSeconds @@ -182,6 +200,14 @@ label: HttpGet sort: 100 subParameters: + - 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 @@ -211,14 +237,6 @@ 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: KV validate: {} - description: Number of seconds after the container is started before the first @@ -247,24 +265,6 @@ 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 - and the httpGet attribute. - jsonKey: tcpSocket - label: TcpSocket - sort: 100 - subParameters: - - description: The TCP socket within the container that should be probed to assess - container health. - jsonKey: port - label: Port - sort: 100 - uiType: Number - validate: - required: true - uiType: KV - validate: {} uiType: Group validate: {} - description: Instructions for assessing whether the container is alive. @@ -272,6 +272,52 @@ label: LivenessProbe检测 sort: 15 subParameters: + - 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 + and the httpGet attribute. + jsonKey: tcpSocket + label: TcpSocket + sort: 100 + subParameters: + - description: The TCP socket within the container that should be probed to assess + container health. + jsonKey: port + label: Port + sort: 100 + uiType: Number + validate: + required: true + uiType: KV + 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: KV + validate: {} - description: Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe). jsonKey: failureThreshold @@ -354,52 +400,6 @@ 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 - and the httpGet attribute. - jsonKey: tcpSocket - label: TcpSocket - sort: 100 - subParameters: - - description: The TCP socket within the container that should be probed to assess - container health. - jsonKey: port - label: Port - sort: 100 - uiType: Number - validate: - required: true - uiType: KV - 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: KV - validate: {} uiType: Group validate: {} - description: Specify image pull policy for your service @@ -416,6 +416,12 @@ value: Always - label: 永不更新 value: Never +- description: Specify image pull secrets for your service + jsonKey: imagePullSecrets + label: ImagePullSecrets + sort: 100 + uiType: Strings + validate: {} - description: If addRevisionLabel is true, the appRevision label will be added to the underlying pods jsonKey: addRevisionLabel @@ -425,9 +431,3 @@ validate: defaultValue: false required: true -- description: Specify image pull secrets for your service - jsonKey: imagePullSecrets - label: ImagePullSecrets - sort: 100 - uiType: Strings - validate: {} diff --git a/pkg/apiserver/rest/usecase/workflow.go b/pkg/apiserver/rest/usecase/workflow.go index 6f0682a26..1a6b189a9 100644 --- a/pkg/apiserver/rest/usecase/workflow.go +++ b/pkg/apiserver/rest/usecase/workflow.go @@ -48,20 +48,22 @@ const ( // WorkflowUsecase workflow manage api type WorkflowUsecase interface { - ListApplicationWorkflow(ctx context.Context, app *model.Application, enable *bool) ([]*apisv1.WorkflowBase, error) - GetWorkflow(ctx context.Context, workflowName string) (*model.Workflow, error) + ListApplicationWorkflow(ctx context.Context, app *model.Application) ([]*apisv1.WorkflowBase, error) + GetWorkflow(ctx context.Context, app *model.Application, workflowName string) (*model.Workflow, error) DetailWorkflow(ctx context.Context, workflow *model.Workflow) (*apisv1.DetailWorkflowResponse, error) GetApplicationDefaultWorkflow(ctx context.Context, app *model.Application) (*model.Workflow, error) - DeleteWorkflow(ctx context.Context, workflowName string) error + DeleteWorkflow(ctx context.Context, app *model.Application, workflowName string) error + DeleteWorkflowByApp(ctx context.Context, app *model.Application) error CreateWorkflow(ctx context.Context, app *model.Application, req apisv1.CreateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error) - CreateWorkflowRecord(ctx context.Context, app *v1beta1.Application) error UpdateWorkflow(ctx context.Context, workflow *model.Workflow, req apisv1.UpdateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error) - ListWorkflowRecords(ctx context.Context, workflowName string, page, pageSize int) (*apisv1.ListWorkflowRecordsResponse, error) - DetailWorkflowRecord(ctx context.Context, workflowName, recordName string) (*apisv1.DetailWorkflowRecordResponse, error) + CreateWorkflowRecord(ctx context.Context, appModel *model.Application, app *v1beta1.Application, workflow *model.Workflow) error + ListWorkflowRecords(ctx context.Context, workflow *model.Workflow, page, pageSize int) (*apisv1.ListWorkflowRecordsResponse, error) + DetailWorkflowRecord(ctx context.Context, workflow *model.Workflow, recordName string) (*apisv1.DetailWorkflowRecordResponse, error) SyncWorkflowRecord(ctx context.Context) error - ResumeRecord(ctx context.Context, appModel *model.Application, recordName string) error - TerminateRecord(ctx context.Context, appModel *model.Application, recordName string) error - RollbackRecord(ctx context.Context, appModel *model.Application, recordName, revisionName string) error + ResumeRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error + TerminateRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error + RollbackRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName, revisionName string) error + CountWorkflow(ctx context.Context, app *model.Application) int64 } // NewWorkflowUsecase new workflow usecase @@ -84,9 +86,10 @@ type workflowUsecaseImpl struct { } // DeleteWorkflow delete application workflow -func (w *workflowUsecaseImpl) DeleteWorkflow(ctx context.Context, workflowName string) error { +func (w *workflowUsecaseImpl) DeleteWorkflow(ctx context.Context, app *model.Application, workflowName string) error { var workflow = &model.Workflow{ - Name: workflowName, + Name: workflowName, + AppPrimaryKey: app.PrimaryKey(), } if err := w.ds.Delete(ctx, workflow); err != nil { if errors.Is(err, datastore.ErrRecordNotExist) { @@ -97,7 +100,30 @@ func (w *workflowUsecaseImpl) DeleteWorkflow(ctx context.Context, workflowName s return nil } +func (w *workflowUsecaseImpl) DeleteWorkflowByApp(ctx context.Context, app *model.Application) error { + var workflow = &model.Workflow{ + AppPrimaryKey: app.PrimaryKey(), + } + + workflows, err := w.ds.List(ctx, workflow, &datastore.ListOptions{}) + if err != nil { + if errors.Is(err, datastore.ErrRecordNotExist) { + return nil + } + return err + } + for i := range workflows { + if err := w.ds.Delete(ctx, workflows[i]); err != nil { + log.Logger.Errorf("delete workflow %s failure %s", workflows[i].PrimaryKey(), err.Error()) + } + } + return nil +} + func (w *workflowUsecaseImpl) CreateWorkflow(ctx context.Context, app *model.Application, req apisv1.CreateWorkflowRequest) (*apisv1.DetailWorkflowResponse, error) { + if req.EnvName == "" { + return nil, bcode.ErrWorkflowNoEnv + } var steps []model.WorkflowStep for _, step := range req.Steps { properties, err := model.NewJSONStructByString(step.Properties) @@ -106,11 +132,13 @@ func (w *workflowUsecaseImpl) CreateWorkflow(ctx context.Context, app *model.App return nil, bcode.ErrInvalidProperties } steps = append(steps, model.WorkflowStep{ - Name: step.Name, - Type: step.Type, - Inputs: step.Inputs, - Outputs: step.Outputs, - Properties: properties, + Name: step.Name, + Type: step.Type, + Inputs: step.Inputs, + Outputs: step.Outputs, + Description: step.Description, + DependsOn: step.DependsOn, + Properties: properties, }) } // It is allowed to set multiple workflows as default, and only one takes effect. @@ -155,39 +183,46 @@ func (w *workflowUsecaseImpl) UpdateWorkflow(ctx context.Context, workflow *mode return w.DetailWorkflow(ctx, workflow) } -// DetailWorkflow detail workflow -func (w *workflowUsecaseImpl) DetailWorkflow(ctx context.Context, workflow *model.Workflow) (*apisv1.DetailWorkflowResponse, error) { +func converWorkflowBase(workflow *model.Workflow) apisv1.WorkflowBase { var steps []apisv1.WorkflowStep for _, step := range workflow.Steps { apiStep := apisv1.WorkflowStep{ - Name: step.Name, - Type: step.Type, - Inputs: step.Inputs, - Outputs: step.Outputs, - Properties: step.Properties.JSON(), + Name: step.Name, + Type: step.Type, + Description: step.Description, + Inputs: step.Inputs, + Outputs: step.Outputs, + Properties: step.Properties.JSON(), + DependsOn: step.DependsOn, } if step.Properties != nil { apiStep.Properties = step.Properties.JSON() } steps = append(steps, apiStep) } + return apisv1.WorkflowBase{ + Name: workflow.Name, + Description: workflow.Description, + Default: workflow.Default, + EnvName: workflow.EnvName, + CreateTime: workflow.CreateTime, + UpdateTime: workflow.UpdateTime, + Steps: steps, + } +} + +// DetailWorkflow detail workflow +func (w *workflowUsecaseImpl) DetailWorkflow(ctx context.Context, workflow *model.Workflow) (*apisv1.DetailWorkflowResponse, error) { return &apisv1.DetailWorkflowResponse{ - WorkflowBase: apisv1.WorkflowBase{ - Name: workflow.Name, - Description: workflow.Description, - Default: workflow.Default, - EnvName: workflow.EnvName, - CreateTime: workflow.CreateTime, - UpdateTime: workflow.UpdateTime, - }, - Steps: steps, + WorkflowBase: converWorkflowBase(workflow), }, nil } // GetWorkflow get workflow model -func (w *workflowUsecaseImpl) GetWorkflow(ctx context.Context, workflowName string) (*model.Workflow, error) { +func (w *workflowUsecaseImpl) GetWorkflow(ctx context.Context, app *model.Application, workflowName string) (*model.Workflow, error) { var workflow = model.Workflow{ - Name: workflowName, + Name: workflowName, + AppPrimaryKey: app.PrimaryKey(), } if err := w.ds.Get(ctx, &workflow); err != nil { if errors.Is(err, datastore.ErrRecordNotExist) { @@ -199,7 +234,7 @@ func (w *workflowUsecaseImpl) GetWorkflow(ctx context.Context, workflowName stri } // ListApplicationWorkflow list application workflows -func (w *workflowUsecaseImpl) ListApplicationWorkflow(ctx context.Context, app *model.Application, enable *bool) ([]*apisv1.WorkflowBase, error) { +func (w *workflowUsecaseImpl) ListApplicationWorkflow(ctx context.Context, app *model.Application) ([]*apisv1.WorkflowBase, error) { var workflow = model.Workflow{ AppPrimaryKey: app.PrimaryKey(), } @@ -210,14 +245,8 @@ func (w *workflowUsecaseImpl) ListApplicationWorkflow(ctx context.Context, app * var list []*apisv1.WorkflowBase for _, workflow := range workflows { wm := workflow.(*model.Workflow) - list = append(list, &apisv1.WorkflowBase{ - Name: wm.Name, - Description: wm.Description, - Default: wm.Default, - EnvName: wm.EnvName, - CreateTime: wm.CreateTime, - UpdateTime: wm.UpdateTime, - }) + base := converWorkflowBase(wm) + list = append(list, &base) } return list, nil } @@ -239,9 +268,10 @@ func (w *workflowUsecaseImpl) GetApplicationDefaultWorkflow(ctx context.Context, } // ListWorkflowRecords list workflow record -func (w *workflowUsecaseImpl) ListWorkflowRecords(ctx context.Context, workflowName string, page, pageSize int) (*apisv1.ListWorkflowRecordsResponse, error) { +func (w *workflowUsecaseImpl) ListWorkflowRecords(ctx context.Context, workflow *model.Workflow, page, pageSize int) (*apisv1.ListWorkflowRecordsResponse, error) { var record = model.WorkflowRecord{ - WorkflowPrimaryKey: workflowName, + AppPrimaryKey: workflow.AppPrimaryKey, + WorkflowName: workflow.Name, } records, err := w.ds.List(ctx, &record, &datastore.ListOptions{Page: page, PageSize: pageSize}) if err != nil { @@ -267,13 +297,17 @@ func (w *workflowUsecaseImpl) ListWorkflowRecords(ctx context.Context, workflowN } // DetailWorkflowRecord get workflow record detail with name -func (w *workflowUsecaseImpl) DetailWorkflowRecord(ctx context.Context, workflowName, recordName string) (*apisv1.DetailWorkflowRecordResponse, error) { +func (w *workflowUsecaseImpl) DetailWorkflowRecord(ctx context.Context, workflow *model.Workflow, recordName string) (*apisv1.DetailWorkflowRecordResponse, error) { var record = model.WorkflowRecord{ - WorkflowPrimaryKey: workflowName, - Name: recordName, + AppPrimaryKey: workflow.AppPrimaryKey, + WorkflowName: workflow.Name, + Name: recordName, } err := w.ds.Get(ctx, &record) if err != nil { + if errors.Is(err, datastore.ErrRecordNotExist) { + return nil, bcode.ErrWorkflowRecordNotExist + } return nil, err } @@ -283,6 +317,9 @@ func (w *workflowUsecaseImpl) DetailWorkflowRecord(ctx context.Context, workflow } err = w.ds.Get(ctx, &revision) if err != nil { + if errors.Is(err, datastore.ErrRecordNotExist) { + return nil, bcode.ErrApplicationRevisionNotExist + } return nil, err } @@ -307,23 +344,27 @@ func (w *workflowUsecaseImpl) SyncWorkflowRecord(ctx context.Context) error { for _, item := range records { app := &v1beta1.Application{} - index := item.Index() - appPrimaryKey := index["appPrimaryKey"] - namespace := index["namespace"] - recordName := index["name"] - + record := item.(*model.WorkflowRecord) + workflow := &model.Workflow{ + Name: record.WorkflowName, + AppPrimaryKey: record.AppPrimaryKey, + } + if err := w.ds.Get(ctx, workflow); err != nil { + log.Logger.Errorf("get workflow %s/%s failure %s", record.AppPrimaryKey, record.WorkflowName, err.Error()) + continue + } if err := w.kubeClient.Get(ctx, types.NamespacedName{ - Name: appPrimaryKey, - Namespace: namespace, + Name: converAppName(record.AppPrimaryKey, workflow.EnvName), + Namespace: record.Namespace, }, app); err != nil { - klog.ErrorS(err, "failed to get app", "app name", appPrimaryKey) + klog.ErrorS(err, "failed to get app", "app name", record.AppPrimaryKey) return err } // try to sync the status from the running application - if app.Annotations != nil && app.Annotations[oam.AnnotationPublishVersion] == recordName { - if err := w.syncWorkflowStatus(ctx, app, recordName); err != nil { - klog.ErrorS(err, "failed to sync workflow status", "app name", appPrimaryKey, "workflow record name", recordName) + if app.Annotations != nil && app.Annotations[oam.AnnotationPublishVersion] == record.Name { + if err := w.syncWorkflowStatus(ctx, app, record.Name); err != nil { + klog.ErrorS(err, "failed to sync workflow status", "app name", record.AppPrimaryKey, "workflow record name", record.Name) } continue } @@ -331,19 +372,19 @@ func (w *workflowUsecaseImpl) SyncWorkflowRecord(ctx context.Context) error { // try to sync the status from the controller revision cr := &appsv1.ControllerRevision{} if err := w.kubeClient.Get(ctx, types.NamespacedName{ - Name: fmt.Sprintf("record-%s-%s", appPrimaryKey, recordName), - Namespace: namespace, + Name: fmt.Sprintf("record-%s-%s", record.AppPrimaryKey, record.Name), + Namespace: record.Namespace, }, cr); err != nil { - klog.ErrorS(err, "failed to get controller revision", "app name", appPrimaryKey, "workflow record name", recordName) + klog.ErrorS(err, "failed to get controller revision", "app name", record.AppPrimaryKey, "workflow record name", record.Name) continue } appInRevision, err := util.RawExtension2Application(cr.Data) if err != nil { - klog.ErrorS(err, "failed to get app data in controller revision", "controller revision name", cr.Name, "app name", appPrimaryKey, "workflow record name", recordName) + klog.ErrorS(err, "failed to get app data in controller revision", "controller revision name", cr.Name, "app name", record.AppPrimaryKey, "workflow record name", record.Name) continue } - if err := w.syncWorkflowStatus(ctx, appInRevision, recordName); err != nil { - klog.ErrorS(err, "failed to sync workflow status", "app name", appPrimaryKey, "workflow record version", recordName) + if err := w.syncWorkflowStatus(ctx, appInRevision, record.Name); err != nil { + klog.ErrorS(err, "failed to sync workflow status", "app name", record.AppPrimaryKey, "workflow record version", record.Name) continue } @@ -353,18 +394,26 @@ func (w *workflowUsecaseImpl) SyncWorkflowRecord(ctx context.Context) error { } func (w *workflowUsecaseImpl) syncWorkflowStatus(ctx context.Context, app *v1beta1.Application, recordName string) error { + var record = &model.WorkflowRecord{ - AppPrimaryKey: app.Name, + AppPrimaryKey: app.Annotations[oam.AnnotationAppName], Name: recordName, } if err := w.ds.Get(ctx, record); err != nil { + if errors.Is(err, datastore.ErrRecordNotExist) { + return bcode.ErrWorkflowRecordNotExist + } return err } var revision = &model.ApplicationRevision{ - AppPrimaryKey: app.Name, + AppPrimaryKey: app.Annotations[oam.AnnotationAppName], Version: record.RevisionPrimaryKey, } + if err := w.ds.Get(ctx, revision); err != nil { + if errors.Is(err, datastore.ErrRecordNotExist) { + return bcode.ErrApplicationRevisionNotExist + } return err } @@ -395,13 +444,10 @@ func (w *workflowUsecaseImpl) syncWorkflowStatus(ctx context.Context, app *v1bet return nil } -func (w *workflowUsecaseImpl) CreateWorkflowRecord(ctx context.Context, app *v1beta1.Application) error { +func (w *workflowUsecaseImpl) CreateWorkflowRecord(ctx context.Context, appModel *model.Application, app *v1beta1.Application, workflow *model.Workflow) error { if app.Annotations == nil { return fmt.Errorf("empty annotations in application") } - if app.Annotations[oam.AnnotationWorkflowName] == "" { - return fmt.Errorf("failed to get workflow name from application") - } if app.Annotations[oam.AnnotationPublishVersion] == "" { return fmt.Errorf("failed to get record version from application") } @@ -410,19 +456,26 @@ func (w *workflowUsecaseImpl) CreateWorkflowRecord(ctx context.Context, app *v1b } return w.ds.Add(ctx, &model.WorkflowRecord{ - WorkflowPrimaryKey: app.Annotations[oam.AnnotationWorkflowName], - AppPrimaryKey: app.Name, + WorkflowName: workflow.Name, + AppPrimaryKey: appModel.PrimaryKey(), RevisionPrimaryKey: app.Annotations[oam.AnnotationDeployVersion], Name: app.Annotations[oam.AnnotationPublishVersion], - Namespace: app.Namespace, + Namespace: appModel.Namespace, Finished: "false", StartTime: time.Now().Time, Status: model.RevisionStatusInit, }) } +func (w *workflowUsecaseImpl) CountWorkflow(ctx context.Context, app *model.Application) int64 { + count, err := w.ds.Count(ctx, &model.Workflow{AppPrimaryKey: app.PrimaryKey()}, &datastore.FilterOptions{}) + if err != nil { + log.Logger.Errorf("count app %s workflow failure %s", app.PrimaryKey(), err.Error()) + } + return count +} -func (w *workflowUsecaseImpl) ResumeRecord(ctx context.Context, appModel *model.Application, recordName string) error { - oamApp, err := w.checkRecordRunning(ctx, appModel) +func (w *workflowUsecaseImpl) ResumeRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error { + oamApp, err := w.checkRecordRunning(ctx, appModel, workflow.EnvName) if err != nil { return err } @@ -438,8 +491,8 @@ func (w *workflowUsecaseImpl) ResumeRecord(ctx context.Context, appModel *model. return nil } -func (w *workflowUsecaseImpl) TerminateRecord(ctx context.Context, appModel *model.Application, recordName string) error { - oamApp, err := w.checkRecordRunning(ctx, appModel) +func (w *workflowUsecaseImpl) TerminateRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName string) error { + oamApp, err := w.checkRecordRunning(ctx, appModel, workflow.EnvName) if err != nil { return err } @@ -455,7 +508,7 @@ func (w *workflowUsecaseImpl) TerminateRecord(ctx context.Context, appModel *mod return nil } -func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *model.Application, recordName, revisionVersion string) error { +func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *model.Application, workflow *model.Workflow, recordName, revisionVersion string) error { if revisionVersion == "" { // find the latest complete revision version var revision = model.ApplicationRevision{ @@ -472,23 +525,24 @@ func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *mode return err } if len(revisions) == 0 { - fmt.Errorf("there is no complete revision, please specify a revision version") + return bcode.ErrApplicationNoReadyRevision } revisionVersion = revisions[0].Index()["version"] } - oamApp, err := w.checkRecordRunning(ctx, appModel) - if err != nil { - return err - } - var record = &model.WorkflowRecord{ - AppPrimaryKey: appModel.Name, + AppPrimaryKey: appModel.PrimaryKey(), Name: recordName, } if err := w.ds.Get(ctx, record); err != nil { return err } + + oamApp, err := w.checkRecordRunning(ctx, appModel, workflow.EnvName) + if err != nil { + return err + } + var rollbackRevision = model.ApplicationRevision{ AppPrimaryKey: appModel.Name, Version: revisionVersion, @@ -507,12 +561,11 @@ func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *mode if oamApp.Annotations == nil { oamApp.Annotations = make(map[string]string) } - newRecordName := utils.GenerateVersion(record.WorkflowPrimaryKey) + newRecordName := utils.GenerateVersion(record.WorkflowName) oamApp.Annotations[oam.AnnotationDeployVersion] = revisionVersion oamApp.Annotations[oam.AnnotationPublishVersion] = newRecordName - // create a new workflow record - if err := w.CreateWorkflowRecord(ctx, oamApp); err != nil { + if err := w.CreateWorkflowRecord(ctx, appModel, oamApp, workflow); err != nil { return err } @@ -527,9 +580,9 @@ func (w *workflowUsecaseImpl) RollbackRecord(ctx context.Context, appModel *mode return nil } -func (w *workflowUsecaseImpl) checkRecordRunning(ctx context.Context, appModel *model.Application) (*v1beta1.Application, error) { +func (w *workflowUsecaseImpl) checkRecordRunning(ctx context.Context, appModel *model.Application, envName string) (*v1beta1.Application, error) { oamApp := &v1beta1.Application{} - if err := w.kubeClient.Get(ctx, types.NamespacedName{Name: appModel.Name, Namespace: appModel.Namespace}, oamApp); err != nil { + if err := w.kubeClient.Get(ctx, types.NamespacedName{Name: converAppName(appModel.Name, envName), Namespace: appModel.Namespace}, oamApp); err != nil { return nil, err } if oamApp.Status.Workflow != nil && !oamApp.Status.Workflow.Suspend && !oamApp.Status.Workflow.Terminated && !oamApp.Status.Workflow.Finished { diff --git a/pkg/apiserver/rest/usecase/workflow_test.go b/pkg/apiserver/rest/usecase/workflow_test.go index 7f7305487..799b0bab4 100644 --- a/pkg/apiserver/rest/usecase/workflow_test.go +++ b/pkg/apiserver/rest/usecase/workflow_test.go @@ -37,20 +37,42 @@ import ( "github.com/oam-dev/kubevela/pkg/utils/apply" ) +var appName = "app-workflow" var _ = Describe("Test workflow usecase functions", func() { var ( workflowUsecase *workflowUsecaseImpl + appUsecase *applicationUsecaseImpl ) BeforeEach(func() { workflowUsecase = &workflowUsecaseImpl{ds: ds, kubeClient: k8sClient, apply: apply.NewAPIApplicator(k8sClient)} + appUsecase = &applicationUsecaseImpl{ds: ds, kubeClient: k8sClient, apply: apply.NewAPIApplicator(k8sClient), envBindingUsecase: &envBindingUsecaseImpl{ + ds: ds, + workflowUsecase: workflowUsecase, + }} }) It("Test CreateWorkflow function", func() { + reqApp := apisv1.CreateApplicationRequest{ + Name: appName, + Namespace: "default", + Description: "this is a test app", + EnvBinding: []*apisv1.EnvBinding{{ + Name: "dev", + Description: "dev env", + TargetNames: []string{"dev-target"}, + }}, + } + _, err := appUsecase.CreateApplication(context.TODO(), reqApp) + Expect(err).Should(BeNil()) + req := apisv1.CreateWorkflowRequest{ Name: "test-workflow-1", Description: "this is a workflow", + EnvName: "dev", } + base, err := workflowUsecase.CreateWorkflow(context.TODO(), &model.Application{ - Name: "test-app", + Name: appName, + Namespace: "default", }, req) Expect(err).Should(BeNil()) Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty()) @@ -58,10 +80,12 @@ var _ = Describe("Test workflow usecase functions", func() { req = apisv1.CreateWorkflowRequest{ Name: "test-workflow-2", Description: "this is test workflow", + EnvName: "dev", Default: true, } base, err = workflowUsecase.CreateWorkflow(context.TODO(), &model.Application{ - Name: "test-app", + Name: appName, + Namespace: "default", }, req) Expect(err).Should(BeNil()) Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty()) @@ -69,7 +93,8 @@ var _ = Describe("Test workflow usecase functions", func() { It("Test GetApplicationDefaultWorkflow function", func() { workflow, err := workflowUsecase.GetApplicationDefaultWorkflow(context.TODO(), &model.Application{ - Name: "test-app", + Name: appName, + Namespace: "default", }) Expect(err).Should(BeNil()) Expect(workflow).ShouldNot(BeNil()) @@ -83,14 +108,22 @@ var _ = Describe("Test workflow usecase functions", func() { app := &v1beta1.Application{} err = json.Unmarshal(raw, app) Expect(err).Should(BeNil()) - app.Annotations[oam.AnnotationWorkflowName] = "list-workflow-name" + app.Annotations[oam.AnnotationWorkflowName] = "test-workflow-2" + workflow, err := workflowUsecase.GetWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, "test-workflow-2") + Expect(err).Should(BeNil()) for i := 0; i < 3; i++ { app.Annotations[oam.AnnotationPublishVersion] = fmt.Sprintf("list-workflow-name-%d", i) - err := workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) } - resp, err := workflowUsecase.ListWorkflowRecords(context.TODO(), "list-workflow-name", 0, 10) + resp, err := workflowUsecase.ListWorkflowRecords(context.TODO(), workflow, 0, 10) Expect(err).Should(BeNil()) Expect(resp.Total).Should(Equal(int64(3))) }) @@ -102,28 +135,35 @@ var _ = Describe("Test workflow usecase functions", func() { app := &v1beta1.Application{} err = json.Unmarshal(raw, app) Expect(err).Should(BeNil()) - app.Annotations[oam.AnnotationWorkflowName] = "test-workflow-name" - app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-name-123" + app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-2-123" app.Annotations[oam.AnnotationDeployVersion] = "1234" - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + workflow, err := workflowUsecase.GetWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, "test-workflow-2") + Expect(err).Should(BeNil()) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) var revision = &model.ApplicationRevision{ - AppPrimaryKey: "test", + AppPrimaryKey: appName, Version: "1234", Status: model.RevisionStatusInit, DeployUser: "test-user", Note: "test-commit", TriggerType: "API", - WorkflowName: "test-workflow-name", + WorkflowName: "test-workflow-2", } err = workflowUsecase.createTestApplicationRevision(context.TODO(), revision) Expect(err).Should(BeNil()) - detail, err := workflowUsecase.DetailWorkflowRecord(context.TODO(), "test-workflow-name", "test-workflow-name-123") + detail, err := workflowUsecase.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-123") Expect(err).Should(BeNil()) - Expect(detail.WorkflowRecord.Name).Should(Equal("test-workflow-name-123")) + Expect(detail.WorkflowRecord.Name).Should(Equal("test-workflow-2-123")) Expect(detail.DeployUser).Should(Equal("test-user")) }) @@ -135,19 +175,27 @@ var _ = Describe("Test workflow usecase functions", func() { err = json.Unmarshal(raw, app) Expect(err).Should(BeNil()) app.Status.Workflow.Finished = false - app.Annotations[oam.AnnotationWorkflowName] = "test-workflow-name" - app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-name-233" + app.Annotations[oam.AnnotationWorkflowName] = "test-workflow-2" + app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-2-233" app.Annotations[oam.AnnotationDeployVersion] = "4321" - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + workflow, err := workflowUsecase.GetWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, "test-workflow-2") + Expect(err).Should(BeNil()) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) By("create one revision to test sync workflow record") var revision = &model.ApplicationRevision{ - AppPrimaryKey: "test", + AppPrimaryKey: appName, Version: "4321", Status: model.RevisionStatusInit, DeployUser: "test-user", - WorkflowName: "test-workflow-name", + WorkflowName: "test-workflow-2", } err = workflowUsecase.createTestApplicationRevision(context.TODO(), revision) Expect(err).Should(BeNil()) @@ -158,12 +206,17 @@ var _ = Describe("Test workflow usecase functions", func() { err = workflowUsecase.kubeClient.Create(ctx, app) Expect(err).Should(BeNil()) err = workflowUsecase.kubeClient.Status().Patch(ctx, app, client.Merge) - + Expect(err).Should(BeNil()) err = workflowUsecase.SyncWorkflowRecord(ctx) Expect(err).Should(BeNil()) + workflow, err = workflowUsecase.GetWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, "test-workflow-2") + Expect(err).Should(BeNil()) By("check the record") - record, err := workflowUsecase.DetailWorkflowRecord(context.TODO(), "test-workflow-name", "test-workflow-name-233") + record, err := workflowUsecase.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-233") Expect(err).Should(BeNil()) Expect(record.Status).Should(Equal(model.RevisionStatusComplete)) @@ -174,18 +227,21 @@ var _ = Describe("Test workflow usecase functions", func() { By("create another workflow record to test sync status from controller revision") app.Status.Workflow.Finished = false - app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-name-111" + app.Annotations[oam.AnnotationPublishVersion] = "test-workflow-2-111" app.Annotations[oam.AnnotationDeployVersion] = "1111" - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) By("create another revision to test sync workflow record") var anotherRevision = &model.ApplicationRevision{ - AppPrimaryKey: "test", + AppPrimaryKey: appName, Version: "1111", Status: model.RevisionStatusInit, DeployUser: "test-user", - WorkflowName: "test-workflow-name", + WorkflowName: "test-workflow-2", } err = workflowUsecase.createTestApplicationRevision(context.TODO(), anotherRevision) Expect(err).Should(BeNil()) @@ -194,9 +250,9 @@ var _ = Describe("Test workflow usecase functions", func() { Expect(err).Should(BeNil()) cr := &appsv1.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "record-test-test-workflow-name-111", + Name: "record-" + appName + "-test-workflow-2-111", Namespace: "default", - Labels: map[string]string{"vela.io/wf-revision": "test-workflow-name-111"}, + Labels: map[string]string{"vela.io/wf-revision": "test-workflow-2-111"}, }, Data: runtime.RawExtension{Raw: raw}, } @@ -207,7 +263,7 @@ var _ = Describe("Test workflow usecase functions", func() { Expect(err).Should(BeNil()) By("check the record") - anotherRecord, err := workflowUsecase.DetailWorkflowRecord(context.TODO(), "test-workflow-name", "test-workflow-name-111") + anotherRecord, err := workflowUsecase.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-111") Expect(err).Should(BeNil()) Expect(anotherRecord.Status).Should(Equal(model.RevisionStatusComplete)) @@ -219,88 +275,141 @@ var _ = Describe("Test workflow usecase functions", func() { It("Test ResumeRecord function", func() { ctx := context.TODO() - app, err := createTestSuspendApp(ctx, "resume-app", "revision-resume1", "workflow-resume", "workflow-resume-1", workflowUsecase.kubeClient) + + ResumeWorkflow := "resume-workflow" + req := apisv1.CreateWorkflowRequest{ + Name: ResumeWorkflow, + Description: "this is a workflow", + EnvName: "resume", + } + + base, err := workflowUsecase.CreateWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, req) + Expect(err).Should(BeNil()) + Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty()) + + app, err := createTestSuspendApp(ctx, appName, "resume", "revision-resume1", ResumeWorkflow, "workflow-resume-1", workflowUsecase.kubeClient) Expect(err).Should(BeNil()) - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, &model.Workflow{Name: ResumeWorkflow}) Expect(err).Should(BeNil()) err = workflowUsecase.createTestApplicationRevision(ctx, &model.ApplicationRevision{ - AppPrimaryKey: "resume-app", - - Version: "revision-resume1", - Status: model.RevisionStatusRunning, + AppPrimaryKey: appName, + Version: "revision-resume1", + Status: model.RevisionStatusRunning, }) Expect(err).Should(BeNil()) err = workflowUsecase.ResumeRecord(ctx, &model.Application{ - Name: "resume-app", + Name: appName, Namespace: "default", - }, "workflow-resume-1") + }, &model.Workflow{Name: ResumeWorkflow, EnvName: "resume"}, "workflow-resume-1") Expect(err).Should(BeNil()) - record, err := workflowUsecase.DetailWorkflowRecord(ctx, "workflow-resume", "workflow-resume-1") + record, err := workflowUsecase.DetailWorkflowRecord(ctx, &model.Workflow{Name: ResumeWorkflow, AppPrimaryKey: appName}, "workflow-resume-1") Expect(err).Should(BeNil()) Expect(record.Status).Should(Equal(model.RevisionStatusRunning)) }) It("Test TerminateRecord function", func() { ctx := context.TODO() - app, err := createTestSuspendApp(ctx, "terminate-app", "revision-terminate1", "workflow-terminate", "workflow-terminate-1", workflowUsecase.kubeClient) + + workflowName := "terminate-workflow" + req := apisv1.CreateWorkflowRequest{ + Name: workflowName, + Description: "this is a workflow", + EnvName: "terminate", + } + workflow := &model.Workflow{Name: workflowName, EnvName: "terminate"} + base, err := workflowUsecase.CreateWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, req) + Expect(err).Should(BeNil()) + Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty()) + + app, err := createTestSuspendApp(ctx, appName, "terminate", "revision-terminate1", workflow.Name, "test-workflow-2-1", workflowUsecase.kubeClient) Expect(err).Should(BeNil()) - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) err = workflowUsecase.createTestApplicationRevision(ctx, &model.ApplicationRevision{ - AppPrimaryKey: "terminate-app", + AppPrimaryKey: appName, Version: "revision-terminate1", Status: model.RevisionStatusRunning, }) Expect(err).Should(BeNil()) err = workflowUsecase.TerminateRecord(ctx, &model.Application{ - Name: "terminate-app", + Name: appName, Namespace: "default", - }, "workflow-terminate-1") + }, workflow, "test-workflow-2-1") Expect(err).Should(BeNil()) - record, err := workflowUsecase.DetailWorkflowRecord(ctx, "workflow-terminate", "workflow-terminate-1") + record, err := workflowUsecase.DetailWorkflowRecord(ctx, workflow, "test-workflow-2-1") Expect(err).Should(BeNil()) Expect(record.Status).Should(Equal(model.RevisionStatusTerminated)) }) It("Test RollbackRecord function", func() { ctx := context.TODO() - app, err := createTestSuspendApp(ctx, "rollback-app", "revision-rollback1", "workflow-rollback", "workflow-rollback-1", workflowUsecase.kubeClient) + + workflowName := "rollback-workflow" + req := apisv1.CreateWorkflowRequest{ + Name: workflowName, + Description: "this is a workflow", + EnvName: "rollback", + } + workflow := &model.Workflow{Name: workflowName, EnvName: "rollback"} + base, err := workflowUsecase.CreateWorkflow(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, req) + Expect(err).Should(BeNil()) + Expect(cmp.Diff(base.Name, req.Name)).Should(BeEmpty()) + + app, err := createTestSuspendApp(ctx, appName, "rollback", "revision-rollback1", workflow.Name, "test-workflow-2-2", workflowUsecase.kubeClient) Expect(err).Should(BeNil()) - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) err = workflowUsecase.createTestApplicationRevision(ctx, &model.ApplicationRevision{ - AppPrimaryKey: "rollback-app", + AppPrimaryKey: appName, Version: "revision-rollback1", Status: model.RevisionStatusRunning, }) Expect(err).Should(BeNil()) err = workflowUsecase.createTestApplicationRevision(ctx, &model.ApplicationRevision{ - AppPrimaryKey: "rollback-app", + AppPrimaryKey: appName, Version: "revision-rollback0", - ApplyAppConfig: `{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"annotations":{"app.oam.dev/workflowName":"workflow-rollback","app.oam.dev/deployVersion":"revision-rollback1","vela.io/publish-version":"workflow-rollback1"},"name":"first-vela-app","namespace":"default"},"spec":{"components":[{"name":"express-server","properties":{"image":"crccheck/hello-world","port":8000},"traits":[{"properties":{"domain":"testsvc.example.com","http":{"/":8000}},"type":"ingress-1-20"}],"type":"webservice"}]}}`, + ApplyAppConfig: `{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"annotations":{"app.oam.dev/workflowName":"test-workflow-2-2","app.oam.dev/deployVersion":"revision-rollback1","vela.io/publish-version":"workflow-rollback1"},"name":"first-vela-app","namespace":"default"},"spec":{"components":[{"name":"express-server","properties":{"image":"crccheck/hello-world","port":8000},"traits":[{"properties":{"domain":"testsvc.example.com","http":{"/":8000}},"type":"ingress-1-20"}],"type":"webservice"}]}}`, Status: model.RevisionStatusComplete, }) Expect(err).Should(BeNil()) err = workflowUsecase.RollbackRecord(ctx, &model.Application{ - Name: "rollback-app", + Name: appName, Namespace: "default", - }, "workflow-rollback-1", "revision-rollback0") + }, workflow, "test-workflow-2-2", "revision-rollback0") Expect(err).Should(BeNil()) recordsNum, err := workflowUsecase.ds.Count(ctx, &model.WorkflowRecord{ - AppPrimaryKey: "rollback-app", - WorkflowPrimaryKey: "workflow-rollback", + AppPrimaryKey: appName, + WorkflowName: workflow.Name, RevisionPrimaryKey: "revision-rollback0", }, nil) Expect(err).Should(BeNil()) @@ -308,18 +417,21 @@ var _ = Describe("Test workflow usecase functions", func() { By("rollback application without revision version") app.Annotations[oam.AnnotationPublishVersion] = "workflow-rollback-2" - err = workflowUsecase.CreateWorkflowRecord(context.TODO(), app) + err = workflowUsecase.CreateWorkflowRecord(context.TODO(), &model.Application{ + Name: appName, + Namespace: "default", + }, app, workflow) Expect(err).Should(BeNil()) err = workflowUsecase.RollbackRecord(ctx, &model.Application{ - Name: "rollback-app", + Name: appName, Namespace: "default", - }, "workflow-rollback-2", "") + }, workflow, "workflow-rollback-2", "") Expect(err).Should(BeNil()) recordsNum, err = workflowUsecase.ds.Count(ctx, &model.WorkflowRecord{ - AppPrimaryKey: "rollback-app", - WorkflowPrimaryKey: "workflow-rollback", + AppPrimaryKey: appName, + WorkflowName: workflow.Name, RevisionPrimaryKey: "revision-rollback0", }, nil) Expect(err).Should(BeNil()) @@ -331,10 +443,11 @@ var yamlStr = `apiVersion: core.oam.dev/v1beta1 kind: Application metadata: annotations: - app.oam.dev/workflowName: test-workflow-name + app.oam.dev/workflowName: test-workflow-2 app.oam.dev/deployVersion: "1234" - vela.io/publish-version: "test-workflow-name-111" - name: test + app.oam.dev/publishVersion: "test-workflow-name-111" + app.oam.dev/appName: "app-workflow" + name: app-workflow-dev namespace: default spec: components: diff --git a/pkg/apiserver/rest/utils/bcode/application.go b/pkg/apiserver/rest/utils/bcode/application.go index 0bc309d57..73075b356 100644 --- a/pkg/apiserver/rest/utils/bcode/application.go +++ b/pkg/apiserver/rest/utils/bcode/application.go @@ -66,3 +66,12 @@ var ErrTraitNotExist = NewBcode(400, 10015, "trait is not exist") // ErrTraitAlreadyExist trait is already exist var ErrTraitAlreadyExist = NewBcode(400, 10016, "trait is already exist") + +// ErrApplicationNoReadyRevision application not have ready revision +var ErrApplicationNoReadyRevision = NewBcode(400, 10017, "application not have ready revision") + +// ErrApplicationRevisionNotExist application revision is not exist +var ErrApplicationRevisionNotExist = NewBcode(404, 10018, "application revision is not exist") + +// ErrApplicationRefusedDelete The application cannot be deleted because it has been deployed +var ErrApplicationRefusedDelete = NewBcode(400, 10019, "The application cannot be deleted because it has been deployed") diff --git a/pkg/apiserver/rest/utils/bcode/workflow.go b/pkg/apiserver/rest/utils/bcode/workflow.go index 9b4059cf3..7296bac83 100644 --- a/pkg/apiserver/rest/utils/bcode/workflow.go +++ b/pkg/apiserver/rest/utils/bcode/workflow.go @@ -27,3 +27,9 @@ var ErrWorkflowNoDefault = NewBcode(404, 20004, "application default workflow is // ErrMustQueryByApp you can only query the Workflow list based on applications. var ErrMustQueryByApp = NewBcode(404, 20005, "you can only query the Workflow list based on applications.") + +// ErrWorkflowNoEnv workflow have not env +var ErrWorkflowNoEnv = NewBcode(400, 20006, "workflow must set env name") + +// ErrWorkflowRecordNotExist workflow record is not exist +var ErrWorkflowRecordNotExist = NewBcode(404, 20007, "workflow record is not exist") diff --git a/pkg/apiserver/rest/webservice/application.go b/pkg/apiserver/rest/webservice/application.go index a55b96b45..94e0e9772 100644 --- a/pkg/apiserver/rest/webservice/application.go +++ b/pkg/apiserver/rest/webservice/application.go @@ -5,7 +5,7 @@ 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 + http://wwc.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, @@ -32,13 +32,18 @@ import ( ) type applicationWebService struct { + workflowWebService applicationUsecase usecase.ApplicationUsecase envBindingUsecase usecase.EnvBindingUsecase } // NewApplicationWebService new application manage webservice -func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase, envBindingUsecase usecase.EnvBindingUsecase) WebService { +func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase, envBindingUsecase usecase.EnvBindingUsecase, workflowUsecase usecase.WorkflowUsecase) WebService { return &applicationWebService{ + workflowWebService: workflowWebService{ + workflowUsecase: workflowUsecase, + applicationUsecase: applicationUsecase, + }, applicationUsecase: applicationUsecase, envBindingUsecase: envBindingUsecase, } @@ -89,15 +94,23 @@ func (c *applicationWebService) GetWebService() *restful.WebService { Returns(400, "", bcode.Bcode{}). Writes(apis.DetailApplicationResponse{})) - ws.Route(ws.GET("/{name}/envs/{envName}/status").To(c.getApplicationStatus). - Doc("get application status"). + 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")). - Param(ws.PathParameter("envName", "identifier of the application envbinding").DataType("string")). - Returns(200, "", apis.ApplicationStatusResponse{}). + Reads(apis.UpdateApplicationRequest{}). + Returns(200, "", apis.ApplicationBase{}). Returns(400, "", bcode.Bcode{}). - Writes(apis.ApplicationStatusResponse{})) + Writes(apis.ApplicationBase{})) + ws.Route(ws.GET("/{name}/statistics").To(c.applicationStatistics). + Doc("detail one application "). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Param(ws.PathParameter("name", "identifier of the application ").DataType("string")). + Returns(200, "", apis.ApplicationStatisticsResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.ApplicationStatisticsResponse{})) ws.Route(ws.PUT("/{name}").To(c.updateApplication). Doc("update one application "). @@ -149,7 +162,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService { Writes(apis.ComponentBase{})) ws.Route(ws.GET("/{name}/components/{componentName}").To(c.detailComponent). - Doc("detail component for application "). + Doc("detail component for application "). Filter(c.appCheckFilter). Param(ws.PathParameter("name", "identifier of the application ").DataType("string")). Metadata(restfulspec.KeyOpenAPITags, tags). @@ -157,6 +170,17 @@ func (c *applicationWebService) GetWebService() *restful.WebService { Returns(400, "", bcode.Bcode{}). Writes(apis.DetailComponentResponse{})) + ws.Route(ws.PUT("/{name}/components/{componentName}").To(c.updateComponent). + Doc("update component config"). + Filter(c.appCheckFilter). + Filter(c.componentCheckFilter). + Param(ws.PathParameter("name", "identifier of the application").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.UpdateApplicationComponentRequest{}). + Returns(200, "", apis.ComponentBase{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.ComponentBase{})) + ws.Route(ws.GET("/{name}/policies").To(c.listApplicationPolicies). Doc("list policy for application"). Filter(c.appCheckFilter). @@ -210,6 +234,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService { ws.Route(ws.POST("/{name}/components/{compName}/traits").To(c.addApplicationTrait). Doc("add trait for a component"). Filter(c.appCheckFilter). + Filter(c.componentCheckFilter). Param(ws.PathParameter("name", "identifier of the application").DataType("string")). Param(ws.PathParameter("compName", "identifier of the component").DataType("string")). Metadata(restfulspec.KeyOpenAPITags, tags). @@ -221,6 +246,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService { ws.Route(ws.PUT("/{name}/components/{compName}/traits/{traitType}").To(c.updateApplicationTrait). Doc("update trait from a component"). Filter(c.appCheckFilter). + Filter(c.componentCheckFilter). Param(ws.PathParameter("name", "identifier of the application").DataType("string")). Param(ws.PathParameter("compName", "identifier of the component").DataType("string")). Param(ws.PathParameter("traitType", "identifier of the type of trait").DataType("string")). @@ -233,6 +259,7 @@ func (c *applicationWebService) GetWebService() *restful.WebService { ws.Route(ws.DELETE("/{name}/components/{compName}/traits/{traitType}").To(c.deleteApplicationTrait). Doc("delete trait from a component"). Filter(c.appCheckFilter). + Filter(c.componentCheckFilter). Param(ws.PathParameter("name", "identifier of the application").DataType("string")). Param(ws.PathParameter("compName", "identifier of the component").DataType("string")). Param(ws.PathParameter("traitType", "identifier of the type of trait").DataType("string")). @@ -306,6 +333,138 @@ func (c *applicationWebService) GetWebService() *restful.WebService { Returns(404, "", bcode.Bcode{}). Writes(apis.EmptyResponse{})) + ws.Route(ws.GET("/{name}/envs/{envName}/status").To(c.getApplicationStatus). + Doc("get application status"). + 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.ApplicationStatusResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.ApplicationStatusResponse{})) + + ws.Route(ws.POST("/{name}/envs/{envName}/recycle").To(c.recycleApplicationEnv). + Doc("get application status"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.envCheckFilter). + Param(ws.PathParameter("name", "identifier of the application ").DataType("string").Required(true)). + Param(ws.PathParameter("envName", "identifier of the application envbinding").DataType("string").Required(true)). + Returns(200, "", apis.EmptyResponse{}). + Returns(400, "", bcode.Bcode{}). + Writes(apis.EmptyResponse{})) + + ws.Route(ws.GET("/{name}/workflows").To(c.listApplicationWorkflows). + Doc("list application workflow"). + Filter(c.appCheckFilter). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "", apis.ListWorkflowResponse{}). + Writes(apis.ListWorkflowResponse{}).Do(returns200, returns500)) + + ws.Route(ws.POST("/{name}/workflows").To(c.createApplicationWorkflow). + Doc("create application workflow"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.CreateWorkflowRequest{}). + Filter(c.appCheckFilter). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Returns(200, "create success", apis.DetailWorkflowResponse{}). + Returns(400, "create failure", bcode.Bcode{}). + Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500)) + + ws.Route(ws.GET("/{name}/workflows/{workflowName}").To(c.detailWorkflow). + Doc("detail application workflow"). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workfloc.").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.workflowCheckFilter). + Returns(200, "create success", apis.DetailWorkflowResponse{}). + Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500)) + + ws.Route(ws.PUT("/{name}/workflows/{workflowName}").To(c.updateWorkflow). + Doc("update application workflow config"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Reads(apis.UpdateWorkflowRequest{}). + Returns(200, "", apis.DetailWorkflowResponse{}). + Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500)) + + ws.Route(ws.DELETE("/{name}/workflows/{workflowName}").To(c.deleteWorkflow). + Doc("deletet workflow"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Returns(200, "", apis.EmptyResponse{}). + Writes(apis.EmptyResponse{}).Do(returns200, returns500)) + + ws.Route(ws.GET("/{name}/workflows/{workflowName}/records").To(c.listWorkflowRecords). + Doc("query application workflow execution record"). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + 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)) + + ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}").To(c.detailWorkflowRecord). + Doc("query application workflow execution record detail"). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Returns(200, "", apis.DetailWorkflowRecordResponse{}). + Writes(apis.DetailWorkflowRecordResponse{}).Do(returns200, returns500)) + + ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}/resume").To(c.resumeWorkflowRecord). + Doc("resume suspend workflow record"). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Returns(200, "", nil). + Returns(400, "", bcode.Bcode{}). + Writes(apis.DetailWorkflowRecordResponse{})) + + ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}/terminate").To(c.terminateWorkflowRecord). + Doc("terminate suspend workflow record"). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Returns(200, "", nil). + Returns(400, "", bcode.Bcode{}). + Writes(apis.DetailWorkflowRecordResponse{})) + + ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}/rollback").To(c.rollbackWorkflowRecord). + Doc("rollback suspend application record"). + Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)). + Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")). + Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). + Param(ws.QueryParameter("rollbackVersion", "identifier of the rollback revision").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Filter(c.appCheckFilter). + Filter(c.workflowCheckFilter). + Returns(200, "", nil). + Returns(400, "", bcode.Bcode{}). + Writes(apis.DetailWorkflowRecordResponse{})) + return ws } @@ -465,6 +624,30 @@ func (c *applicationWebService) detailComponent(req *restful.Request, res *restf } } +func (c *applicationWebService) updateComponent(req *restful.Request, res *restful.Response) { + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + component := req.Request.Context().Value(&apis.CtxKeyApplicationComponent).(*model.ApplicationComponent) + // Verify the validity of parameters + var updateReq apis.UpdateApplicationComponentRequest + 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 + } + base, err := c.applicationUsecase.UpdateComponent(req.Request.Context(), app, component, updateReq) + 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) createApplicationPolicy(req *restful.Request, res *restful.Response) { app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) // Verify the validity of parameters @@ -641,7 +824,7 @@ func (c *applicationWebService) getApplicationStatus(req *restful.Request, res * return } - if err := res.WriteEntity(apis.ApplicationStatusResponse{Status: status}); err != nil { + if err := res.WriteEntity(apis.ApplicationStatusResponse{Status: status, EnvName: req.PathParameter("envName")}); err != nil { bcode.ReturnError(req, res, err) return } @@ -759,6 +942,17 @@ func (c *applicationWebService) appCheckFilter(req *restful.Request, res *restfu chain.ProcessFilter(req, res) } +func (c *applicationWebService) componentCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) { + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + component, err := c.applicationUsecase.GetApplicationComponent(req.Request.Context(), app, req.PathParameter("compName")) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyApplicationComponent, component)) + 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) @@ -775,3 +969,30 @@ func (c *applicationWebService) envCheckFilter(req *restful.Request, res *restfu } bcode.ReturnError(req, res, bcode.ErrApplicationNotEnv) } + +func (c *applicationWebService) applicationStatistics(req *restful.Request, res *restful.Response) { + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + detail, err := c.applicationUsecase.Statistics(req.Request.Context(), app) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := res.WriteEntity(detail); err != nil { + bcode.ReturnError(req, res, err) + return + } +} + +func (c *applicationWebService) recycleApplicationEnv(req *restful.Request, res *restful.Response) { + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + env := req.Request.Context().Value(&apis.CtxKeyApplicationEnvBinding).(*model.EnvBinding) + err := c.envBindingUsecase.ApplicationEnvRecycle(req.Request.Context(), app, env) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := res.WriteEntity(apis.EmptyResponse{}); err != nil { + bcode.ReturnError(req, res, err) + return + } +} diff --git a/pkg/apiserver/rest/webservice/delivery_target.go b/pkg/apiserver/rest/webservice/delivery_target.go index 5a8843ca9..814e1ace6 100644 --- a/pkg/apiserver/rest/webservice/delivery_target.go +++ b/pkg/apiserver/rest/webservice/delivery_target.go @@ -177,7 +177,7 @@ func (dt *DeliveryTargetWebService) updateDeliveryTarget(req *restful.Request, r func (dt *DeliveryTargetWebService) deleteDeliveryTarget(req *restful.Request, res *restful.Response) { deliveryTargetName := req.PathParameter("name") // deliveryTarget in use, can't be deleted - applications, err := dt.applicationUsecase.ListApplications(context.TODO(), apis.ListApplicatioOptions{TargetName: deliveryTargetName}) + applications, err := dt.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicatioOptions{TargetName: deliveryTargetName}) if err != nil { if !errors.Is(err, datastore.ErrRecordNotExist) { bcode.ReturnError(req, res, err) diff --git a/pkg/apiserver/rest/webservice/webservice.go b/pkg/apiserver/rest/webservice/webservice.go index 047b38657..907e41f2e 100644 --- a/pkg/apiserver/rest/webservice/webservice.go +++ b/pkg/apiserver/rest/webservice/webservice.go @@ -69,14 +69,13 @@ func Init(ds datastore.DataStore) { envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase) applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, deliveryTargetUsecase) RegistWebService(NewClusterWebService(clusterUsecase)) - RegistWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase)) + RegistWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase, workflowUsecase)) RegistWebService(NewNamespaceWebService(namespaceUsecase)) RegistWebService(NewDefinitionWebservice(definitionUsecase)) RegistWebService(NewAddonWebService(addonUsecase)) RegistWebService(NewAddonRegistryWebService(addonUsecase)) RegistWebService(NewOAMApplication(oamApplicationUsecase)) RegistWebService(&policyDefinitionWebservice{}) - RegistWebService(NewWorkflowWebService(workflowUsecase, applicationUsecase)) RegistWebService(NewDeliveryTargetWebService(deliveryTargetUsecase, applicationUsecase)) RegistWebService(NewVelaQLWebService(velaQLUsecase)) } diff --git a/pkg/apiserver/rest/webservice/workflow.go b/pkg/apiserver/rest/webservice/workflow.go index be92b120b..d09e427ec 100644 --- a/pkg/apiserver/rest/webservice/workflow.go +++ b/pkg/apiserver/rest/webservice/workflow.go @@ -18,9 +18,7 @@ package webservice import ( "context" - "strconv" - restfulspec "github.com/emicklei/go-restful-openapi/v2" restful "github.com/emicklei/go-restful/v3" "github.com/oam-dev/kubevela/pkg/apiserver/log" @@ -31,122 +29,14 @@ import ( "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode" ) -// NewWorkflowWebService new workflow webservice -func NewWorkflowWebService(workflowUsecase usecase.WorkflowUsecase, applicationUsecase usecase.ApplicationUsecase) WebService { - return &workflowWebService{ - workflowUsecase: workflowUsecase, - applicationUsecase: applicationUsecase, - } -} - type workflowWebService struct { workflowUsecase usecase.WorkflowUsecase applicationUsecase usecase.ApplicationUsecase } -func (w *workflowWebService) GetWebService() *restful.WebService { - ws := new(restful.WebService) - ws.Path(versionPrefix+"/workflows"). - Consumes(restful.MIME_XML, restful.MIME_JSON). - Produces(restful.MIME_JSON, restful.MIME_XML). - Doc("api for cluster manage") - - tags := []string{"workflow"} - - ws.Route(ws.GET("/").To(w.listApplicationWorkflows). - Doc("list application workflow"). - Param(ws.QueryParameter("appName", "identifier of the application.").DataType("string").Required(true)). - Param(ws.QueryParameter("enable", "query based on enable status").DataType("boolean")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Returns(200, "", apis.ListWorkflowResponse{}). - Writes(apis.ListWorkflowResponse{}).Do(returns200, returns500)) - - ws.Route(ws.POST("/").To(w.createApplicationWorkflow). - Doc("create application workflow"). - Metadata(restfulspec.KeyOpenAPITags, tags). - Reads(apis.CreateWorkflowRequest{}). - Returns(200, "create success", apis.DetailWorkflowResponse{}). - Returns(400, "create failure", bcode.Bcode{}). - Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500)) - - ws.Route(ws.GET("/{name}").To(w.detailWorkflow). - Doc("detail application workflow"). - Param(ws.PathParameter("name", "identifier of the workflow.").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.workflowCheckFilter). - Returns(200, "create success", apis.DetailWorkflowResponse{}). - Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500)) - - ws.Route(ws.PUT("/{name}").To(w.updateWorkflow). - Doc("update application workflow config"). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.workflowCheckFilter). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Reads(apis.UpdateWorkflowRequest{}). - Returns(200, "", apis.DetailWorkflowResponse{}). - Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500)) - - ws.Route(ws.DELETE("/{name}").To(w.deleteWorkflow). - Doc("deletet workflow"). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.workflowCheckFilter). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Returns(200, "", apis.EmptyResponse{}). - Writes(apis.EmptyResponse{}).Do(returns200, returns500)) - - ws.Route(ws.GET("/{name}/records").To(w.listWorkflowRecords). - Doc("query application workflow execution record"). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.workflowCheckFilter). - 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)) - - ws.Route(ws.GET("/{name}/records/{record}").To(w.detailWorkflowRecord). - Doc("query application workflow execution record detail"). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Returns(200, "", apis.DetailWorkflowRecordResponse{}). - Writes(apis.DetailWorkflowRecordResponse{}).Do(returns200, returns500)) - - ws.Route(ws.GET("/{name}/records/{record}/resume").To(w.resumeWorkflowRecord). - Doc("resume suspend workflow record"). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.applicationCheckFilter). - Returns(200, "", nil). - Returns(400, "", bcode.Bcode{}). - Writes(apis.DetailWorkflowRecordResponse{})) - - ws.Route(ws.GET("/{name}/records/{record}/terminate").To(w.terminateWorkflowRecord). - Doc("terminate suspend workflow record"). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.applicationCheckFilter). - Returns(200, "", nil). - Returns(400, "", bcode.Bcode{}). - Writes(apis.DetailWorkflowRecordResponse{})) - - ws.Route(ws.GET("/{name}/records/{record}/rollback").To(w.rollbackWorkflowRecord). - Doc("rollback suspend application record"). - Param(ws.PathParameter("name", "identifier of the workflow").DataType("string")). - Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")). - Param(ws.QueryParameter("rollbackVersion", "identifier of the rollback revision").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Filter(w.applicationCheckFilter). - Returns(200, "", nil). - Returns(400, "", bcode.Bcode{}). - Writes(apis.DetailWorkflowRecordResponse{})) - return ws -} - func (w *workflowWebService) workflowCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) { - workflow, err := w.workflowUsecase.GetWorkflow(req.Request.Context(), req.PathParameter("name")) + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + workflow, err := w.workflowUsecase.GetWorkflow(req.Request.Context(), app, req.PathParameter("workflowName")) if err != nil { bcode.ReturnError(req, res, err) return @@ -156,37 +46,19 @@ func (w *workflowWebService) workflowCheckFilter(req *restful.Request, res *rest } func (w *workflowWebService) applicationCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) { - workflow, err := w.workflowUsecase.GetWorkflow(req.Request.Context(), req.PathParameter("name")) + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + workflow, err := w.workflowUsecase.GetWorkflow(req.Request.Context(), app, req.PathParameter("workflowName")) if err != nil { bcode.ReturnError(req, res, err) return } - - app, err := w.applicationUsecase.GetApplication(req.Request.Context(), workflow.AppPrimaryKey) - if err != nil { - bcode.ReturnError(req, res, err) - return - } - req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyApplication, app)) + req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyWorkflow, workflow)) chain.ProcessFilter(req, res) } func (w *workflowWebService) listApplicationWorkflows(req *restful.Request, res *restful.Response) { - if req.QueryParameter("appName") == "" { - bcode.ReturnError(req, res, bcode.ErrMustQueryByApp) - return - } - app, err := w.applicationUsecase.GetApplication(req.Request.Context(), req.QueryParameter("appName")) - if err != nil { - bcode.ReturnError(req, res, err) - return - } - var enableQuery *bool - enable, err := strconv.ParseBool(req.QueryParameter("enable")) - if err == nil { - enableQuery = &enable - } - workflows, err := w.workflowUsecase.ListApplicationWorkflow(req.Request.Context(), app, enableQuery) + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + workflows, err := w.workflowUsecase.ListApplicationWorkflow(req.Request.Context(), app) if err != nil { bcode.ReturnError(req, res, err) return @@ -265,7 +137,8 @@ func (w *workflowWebService) updateWorkflow(req *restful.Request, res *restful.R } func (w *workflowWebService) deleteWorkflow(req *restful.Request, res *restful.Response) { - if err := w.workflowUsecase.DeleteWorkflow(req.Request.Context(), req.PathParameter("name")); err != nil { + app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) + if err := w.workflowUsecase.DeleteWorkflow(req.Request.Context(), app, req.PathParameter("workflowName")); err != nil { bcode.ReturnError(req, res, err) return } @@ -281,8 +154,8 @@ func (w *workflowWebService) listWorkflowRecords(req *restful.Request, res *rest bcode.ReturnError(req, res, err) return } - - records, err := w.workflowUsecase.ListWorkflowRecords(req.Request.Context(), req.PathParameter("name"), page, pageSize) + workflow := req.Request.Context().Value(&apis.CtxKeyWorkflow).(*model.Workflow) + records, err := w.workflowUsecase.ListWorkflowRecords(req.Request.Context(), workflow, page, pageSize) if err != nil { bcode.ReturnError(req, res, err) return @@ -295,7 +168,8 @@ func (w *workflowWebService) listWorkflowRecords(req *restful.Request, res *rest } func (w *workflowWebService) detailWorkflowRecord(req *restful.Request, res *restful.Response) { - record, err := w.workflowUsecase.DetailWorkflowRecord(req.Request.Context(), req.PathParameter("name"), req.PathParameter("record")) + workflow := req.Request.Context().Value(&apis.CtxKeyWorkflow).(*model.Workflow) + record, err := w.workflowUsecase.DetailWorkflowRecord(req.Request.Context(), workflow, req.PathParameter("record")) if err != nil { bcode.ReturnError(req, res, err) return @@ -309,7 +183,8 @@ func (w *workflowWebService) detailWorkflowRecord(req *restful.Request, res *res func (w *workflowWebService) resumeWorkflowRecord(req *restful.Request, res *restful.Response) { app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) - err := w.workflowUsecase.ResumeRecord(req.Request.Context(), app, req.PathParameter("record")) + workflow := req.Request.Context().Value(&apis.CtxKeyWorkflow).(*model.Workflow) + err := w.workflowUsecase.ResumeRecord(req.Request.Context(), app, workflow, req.PathParameter("record")) if err != nil { bcode.ReturnError(req, res, err) return @@ -319,7 +194,8 @@ func (w *workflowWebService) resumeWorkflowRecord(req *restful.Request, res *res func (w *workflowWebService) terminateWorkflowRecord(req *restful.Request, res *restful.Response) { app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) - err := w.workflowUsecase.TerminateRecord(req.Request.Context(), app, req.PathParameter("record")) + workflow := req.Request.Context().Value(&apis.CtxKeyWorkflow).(*model.Workflow) + err := w.workflowUsecase.TerminateRecord(req.Request.Context(), app, workflow, req.PathParameter("record")) if err != nil { bcode.ReturnError(req, res, err) return @@ -329,7 +205,8 @@ func (w *workflowWebService) terminateWorkflowRecord(req *restful.Request, res * func (w *workflowWebService) rollbackWorkflowRecord(req *restful.Request, res *restful.Response) { app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application) - err := w.workflowUsecase.RollbackRecord(req.Request.Context(), app, req.PathParameter("record"), req.QueryParameter("rollbackVersion")) + workflow := req.Request.Context().Value(&apis.CtxKeyWorkflow).(*model.Workflow) + err := w.workflowUsecase.RollbackRecord(req.Request.Context(), app, workflow, req.PathParameter("record"), req.QueryParameter("rollbackVersion")) if err != nil { bcode.ReturnError(req, res, err) return diff --git a/pkg/oam/labels.go b/pkg/oam/labels.go index 45333220f..23e887c53 100644 --- a/pkg/oam/labels.go +++ b/pkg/oam/labels.go @@ -133,11 +133,17 @@ const ( AnnotationDeployVersion = "app.oam.dev/deployVersion" // AnnotationPublishVersion is annotation that record the application workflow version. - AnnotationPublishVersion = "vela.io/publish-version" + AnnotationPublishVersion = "app.oam.dev/publishVersion" // AnnotationWorkflowName specifies the workflow name for execution. AnnotationWorkflowName = "app.oam.dev/workflowName" + // AnnotationAppName specifies the name for application in db. + AnnotationAppName = "app.oam.dev/appName" + + // AnnotationAppAlias specifies the alias for application in db. + AnnotationAppAlias = "app.oam.dev/appAlias" + // AnnotationWorkloadGVK indicates the managed workload's GVK by trait AnnotationWorkloadGVK = "trait.oam.dev/workload-gvk" diff --git a/test/e2e-apiserver-test/application_test.go b/test/e2e-apiserver-test/application_test.go index 4b107b2ff..62192b71e 100644 --- a/test/e2e-apiserver-test/application_test.go +++ b/test/e2e-apiserver-test/application_test.go @@ -33,12 +33,15 @@ import ( apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1" ) +var appName = "app-e2e" +var appProject = "test-app-project" + var _ = Describe("Test application rest api", func() { It("Test create app", func() { defer GinkgoRecover() var req = apisv1.CreateApplicationRequest{ - Name: "test-app-sadasd", - Namespace: "test-app-namespace", + Name: appName, + Namespace: appProject, Description: "this is a test app", Icon: "", Labels: map[string]string{"test": "true"}, @@ -63,7 +66,7 @@ var _ = Describe("Test application rest api", func() { It("Test delete app", func() { defer GinkgoRecover() - req, err := http.NewRequest(http.MethodDelete, "http://127.0.0.1:8000/api/v1/applications/test-app-sadasd", nil) + req, err := http.NewRequest(http.MethodDelete, "http://127.0.0.1:8000/api/v1/applications/"+appName, nil) Expect(err).ShouldNot(HaveOccurred()) res, err := http.DefaultClient.Do(req) Expect(err).ShouldNot(HaveOccurred()) @@ -76,8 +79,8 @@ var _ = Describe("Test application rest api", func() { bs, err := ioutil.ReadFile("./testdata/example-app.yaml") Expect(err).Should(Succeed()) var req = apisv1.CreateApplicationRequest{ - Name: "test-app-sadasd", - Namespace: "test-app-namespace", + Name: appName, + Namespace: appProject, Description: "this is a test app", Icon: "", Labels: map[string]string{"test": "true"}, @@ -102,7 +105,7 @@ var _ = Describe("Test application rest api", func() { It("Test list components", func() { defer GinkgoRecover() - res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/components") + res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/" + appName + "/components") Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -114,37 +117,9 @@ var _ = Describe("Test application rest api", func() { Expect(cmp.Diff(len(components.Components), 2)).Should(BeEmpty()) }) - It("Test list policies", func() { - defer GinkgoRecover() - res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies") - Expect(err).ShouldNot(HaveOccurred()) - Expect(res).ShouldNot(BeNil()) - Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) - Expect(res.Body).ShouldNot(BeNil()) - defer res.Body.Close() - var policies apisv1.ListApplicationPolicy - err = json.NewDecoder(res.Body).Decode(&policies) - Expect(err).ShouldNot(HaveOccurred()) - Expect(cmp.Diff(len(policies.Policies), 1)).Should(BeEmpty()) - }) - - It("Test get workflow", func() { - // defer GinkgoRecover() - // res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies") - // Expect(err).ShouldNot(HaveOccurred()) - // Expect(res).ShouldNot(BeNil()) - // Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) - // Expect(res.Body).ShouldNot(BeNil()) - // defer res.Body.Close() - // var policies apisv1.ListApplicationPolicy - // err = json.NewDecoder(res.Body).Decode(&policies) - // Expect(err).ShouldNot(HaveOccurred()) - // Expect(cmp.Diff(len(policies.Policies), 1)).Should(BeEmpty()) - }) - It("Test detail application", func() { defer GinkgoRecover() - res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd") + res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/" + appName) Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -153,19 +128,53 @@ var _ = Describe("Test application rest api", func() { var detail apisv1.DetailApplicationResponse err = json.NewDecoder(res.Body).Decode(&detail) Expect(err).ShouldNot(HaveOccurred()) - Expect(cmp.Diff(len(detail.Policies), 1)).Should(BeEmpty()) + Expect(cmp.Diff(len(detail.Policies), 0)).Should(BeEmpty()) }) It("Test deploy application", func() { defer GinkgoRecover() - var req = apisv1.ApplicationDeployRequest{ - Note: "test apply", - TriggerType: "web", - Force: false, + var targetName = "dev-default" + var envName = "dev" + var namespace = "default" + // create target + var createTarget = apisv1.CreateDeliveryTargetRequest{ + Name: targetName, + Namespace: appProject, + Cluster: &apisv1.ClusterTarget{ + ClusterName: "local", + Namespace: namespace, + }, } - bodyByte, err := json.Marshal(req) + bodyByte, err := json.Marshal(createTarget) Expect(err).ShouldNot(HaveOccurred()) - res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/deploy", "application/json", bytes.NewBuffer(bodyByte)) + res, err := http.Post("http://127.0.0.1:8000/api/v1/deliveryTargets", "application/json", bytes.NewBuffer(bodyByte)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(res).ShouldNot(BeNil()) + Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) + + // create env + var createEnvReq = apisv1.CreateApplicationEnvRequest{ + EnvBinding: apisv1.EnvBinding{ + Name: envName, + TargetNames: []string{targetName}, + }, + } + bodyByte, err = json.Marshal(createEnvReq) + Expect(err).ShouldNot(HaveOccurred()) + res, err = http.Post("http://127.0.0.1:8000/api/v1/applications/"+appName+"/envs", "application/json", bytes.NewBuffer(bodyByte)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(res).ShouldNot(BeNil()) + + // deploy app + var req = apisv1.ApplicationDeployRequest{ + Note: "test apply", + TriggerType: "web", + WorkflowName: "dev", + Force: false, + } + bodyByte, err = json.Marshal(req) + Expect(err).ShouldNot(HaveOccurred()) + res, err = http.Post("http://127.0.0.1:8000/api/v1/applications/"+appName+"/deploy", "application/json", bytes.NewBuffer(bodyByte)) Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -177,7 +186,7 @@ var _ = Describe("Test application rest api", func() { Expect(cmp.Diff(response.Status, model.RevisionStatusRunning)).Should(BeEmpty()) var oam v1beta1.Application - err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: "test-app-sadasd", Namespace: "test-app-namespace"}, &oam) + err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: appName + "-" + envName, Namespace: appProject}, &oam) Expect(err).Should(BeNil()) Expect(cmp.Diff(len(oam.Spec.Components), 2)).Should(BeEmpty()) Expect(cmp.Diff(len(oam.Spec.Policies), 1)).Should(BeEmpty()) @@ -195,7 +204,7 @@ var _ = Describe("Test application rest api", func() { } bodyByte, err := json.Marshal(req) Expect(err).ShouldNot(HaveOccurred()) - res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/components", "application/json", bytes.NewBuffer(bodyByte)) + res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/"+appName+"/components", "application/json", bytes.NewBuffer(bodyByte)) Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -209,7 +218,7 @@ var _ = Describe("Test application rest api", func() { It("Test detail component", func() { defer GinkgoRecover() - res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/components/test2") + res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/" + appName + "/components/test2") Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -229,7 +238,7 @@ var _ = Describe("Test application rest api", func() { } bodyByte, err := json.Marshal(req) Expect(err).ShouldNot(HaveOccurred()) - res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/components/test2/traits", "application/json", bytes.NewBuffer(bodyByte)) + res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/"+appName+"/components/test2/traits", "application/json", bytes.NewBuffer(bodyByte)) Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -249,7 +258,7 @@ var _ = Describe("Test application rest api", func() { } bodyByte, err := json.Marshal(req2) Expect(err).ShouldNot(HaveOccurred()) - req, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/components/test2/traits/ingress", bytes.NewBuffer(bodyByte)) + req, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/api/v1/applications/"+appName+"/components/test2/traits/ingress", bytes.NewBuffer(bodyByte)) Expect(err).ShouldNot(HaveOccurred()) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) @@ -266,7 +275,7 @@ var _ = Describe("Test application rest api", func() { It("Test delete trait", func() { defer GinkgoRecover() - req, err := http.NewRequest(http.MethodDelete, "http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/components/test2/traits/ingress", nil) + req, err := http.NewRequest(http.MethodDelete, "http://127.0.0.1:8000/api/v1/applications/"+appName+"/components/test2/traits/ingress", nil) Expect(err).ShouldNot(HaveOccurred()) res, err := http.DefaultClient.Do(req) Expect(err).ShouldNot(HaveOccurred()) @@ -283,7 +292,7 @@ var _ = Describe("Test application rest api", func() { } bodyByte, err := json.Marshal(req) Expect(err).ShouldNot(HaveOccurred()) - res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies", "application/json", bytes.NewBuffer(bodyByte)) + res, err := http.Post("http://127.0.0.1:8000/api/v1/applications/"+appName+"/policies", "application/json", bytes.NewBuffer(bodyByte)) Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 400)).Should(BeEmpty()) @@ -295,7 +304,7 @@ var _ = Describe("Test application rest api", func() { } bodyByte2, err := json.Marshal(req2) Expect(err).ShouldNot(HaveOccurred()) - res, err = http.Post("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies", "application/json", bytes.NewBuffer(bodyByte2)) + res, err = http.Post("http://127.0.0.1:8000/api/v1/applications/"+appName+"/policies", "application/json", bytes.NewBuffer(bodyByte2)) Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -310,7 +319,7 @@ var _ = Describe("Test application rest api", func() { It("Test detail application policy", func() { defer GinkgoRecover() - res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies/test2") + res, err := http.Get("http://127.0.0.1:8000/api/v1/applications/" + appName + "/policies/test2") Expect(err).ShouldNot(HaveOccurred()) Expect(res).ShouldNot(BeNil()) Expect(cmp.Diff(res.StatusCode, 200)).Should(BeEmpty()) @@ -330,7 +339,7 @@ var _ = Describe("Test application rest api", func() { } bodyByte2, err := json.Marshal(req2) Expect(err).ShouldNot(HaveOccurred()) - req, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies/test2", bytes.NewBuffer(bodyByte2)) + req, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8000/api/v1/applications/"+appName+"/policies/test2", bytes.NewBuffer(bodyByte2)) Expect(err).ShouldNot(HaveOccurred()) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) @@ -348,7 +357,7 @@ var _ = Describe("Test application rest api", func() { It("Test delete application policy", func() { defer GinkgoRecover() - req, err := http.NewRequest(http.MethodDelete, "http://127.0.0.1:8000/api/v1/applications/test-app-sadasd/policies/test2", nil) + req, err := http.NewRequest(http.MethodDelete, "http://127.0.0.1:8000/api/v1/applications/"+appName+"/policies/test2", nil) Expect(err).ShouldNot(HaveOccurred()) res, err := http.DefaultClient.Do(req) Expect(err).ShouldNot(HaveOccurred()) diff --git a/test/e2e-apiserver-test/suite_test.go b/test/e2e-apiserver-test/suite_test.go index 4371d0bd1..e0364e87b 100644 --- a/test/e2e-apiserver-test/suite_test.go +++ b/test/e2e-apiserver-test/suite_test.go @@ -60,7 +60,7 @@ var _ = BeforeSuite(func() { } cfg.LeaderConfig.ID = uuid.New().String() cfg.LeaderConfig.LockName = "apiserver-lock" - cfg.LeaderConfig.Duration = time.Second * 5 + cfg.LeaderConfig.Duration = time.Second * 10 server, err := arest.New(cfg) Expect(err).ShouldNot(HaveOccurred())