diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml
index ee74c6a7..84cf0540 100644
--- a/.github/workflows/build-and-push.yml
+++ b/.github/workflows/build-and-push.yml
@@ -4,6 +4,9 @@ on:
branches:
- main
- develop
+ - release
+ workflow_dispatch:
+
env:
SERVICE: tks-api
TAG: ${{github.sha}}
@@ -44,9 +47,11 @@ jobs:
if [[ ${{github.ref}} == *"develop"* ]]; then
( cd cicd-manifests/${SERVICE}/overlay/development && kustomize edit set image docker.io/sktcloud/${SERVICE}:${TAG} && git add kustomization.yaml )
+ elif [[ ${{github.ref}} == *"release"* ]]; then
( cd cicd-manifests/${SERVICE}/overlay/ft && kustomize edit set image docker.io/sktcloud/${SERVICE}:${TAG} && git add kustomization.yaml )
elif [[ ${{github.ref}} == *"main"* ]]; then
( cd cicd-manifests/${SERVICE}/overlay/cicd && kustomize edit set image docker.io/sktcloud/${SERVICE}:${TAG} && git add kustomization.yaml )
+ ( cd cicd-manifests/${SERVICE}/overlay/prd && kustomize edit set image docker.io/sktcloud/${SERVICE}:${TAG} && git add kustomization.yaml )
fi
cd cicd-manifests
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 858deeff..e8644715 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,9 +1,10 @@
-name: Build image
+name: Build Image
on:
pull_request_target:
branches:
- main
- develop
+ - release
env:
SERVICE: tks-api
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index deccaf17..5df838e9 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,4 +1,4 @@
-name: golangci-lint
+name: Lint
on:
push:
tags:
@@ -6,12 +6,12 @@ on:
branches:
- main
- develop
- - "release**"
+ - release
pull_request:
branches:
- main
- develop
- - "release**"
+ - release
jobs:
golangci:
name: lint
diff --git a/.gitignore b/.gitignore
index 634da0d9..7da128bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,8 +8,9 @@
*.njsproj
*.sln
*.sw?
+*.sh
web
main
output
-vendor
\ No newline at end of file
+vendor
diff --git a/api/swagger/docs.go b/api/swagger/docs.go
index e7aca169..77172154 100644
--- a/api/swagger/docs.go
+++ b/api/swagger/docs.go
@@ -47,6 +47,39 @@ const docTemplate = `{
"description": "clusterId",
"name": "clusterId",
"in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -488,6 +521,39 @@ const docTemplate = `{
"description": "organizationId",
"name": "organizationId",
"in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -664,6 +730,41 @@ const docTemplate = `{
"Organizations"
],
"summary": "Get organization list",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -855,6 +956,39 @@ const docTemplate = `{
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -1046,14 +1180,48 @@ const docTemplate = `{
"parameters": [
{
"type": "string",
- "description": "organization_Id",
- "name": "organization_Id",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "boolean",
+ "description": "Show all apps including deleted apps",
+ "name": "showAll",
"in": "query"
},
{
"type": "string",
- "description": "show_all",
- "name": "showAll",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
"in": "query"
}
],
@@ -1088,7 +1256,14 @@ const docTemplate = `{
"summary": "Install appServeApp",
"parameters": [
{
- "description": "create appserve request",
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Request body to create app",
"name": "object",
"in": "body",
"required": true,
@@ -1107,14 +1282,14 @@ const docTemplate = `{
}
}
},
- "/organizations/{organizationId}/app-serve-apps/app-id/exist": {
+ "/organizations/{organizationId}/app-serve-apps/count": {
"get": {
"security": [
{
"JWT": []
}
],
- "description": "Get appServeApp by giving params",
+ "description": "Get number of apps on given stack",
"consumes": [
"application/json"
],
@@ -1124,12 +1299,28 @@ const docTemplate = `{
"tags": [
"AppServeApps"
],
- "summary": "Get appServeApp",
+ "summary": "Get number of apps on given stack",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Stack ID",
+ "name": "stackId",
+ "in": "query",
+ "required": true
+ }
+ ],
"responses": {
"200": {
"description": "OK",
"schema": {
- "type": "boolean"
+ "type": "integer"
}
}
}
@@ -1156,7 +1347,7 @@ const docTemplate = `{
"parameters": [
{
"type": "string",
- "description": "organizationId",
+ "description": "Organization ID",
"name": "organizationId",
"in": "path",
"required": true
@@ -1197,6 +1388,22 @@ const docTemplate = `{
"AppServeApps"
],
"summary": "Get appServeApp",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -1225,7 +1432,21 @@ const docTemplate = `{
"summary": "Update appServeApp",
"parameters": [
{
- "description": "update appserve request",
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Request body to update app",
"name": "object",
"in": "body",
"required": true,
@@ -1238,7 +1459,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1262,20 +1483,25 @@ const docTemplate = `{
"summary": "Uninstall appServeApp",
"parameters": [
{
- "description": "body",
- "name": "object",
- "in": "body",
- "required": true,
- "schema": {
- "type": "string"
- }
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1300,6 +1526,13 @@ const docTemplate = `{
],
"summary": "Update app endpoint",
"parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
{
"type": "string",
"description": "appId",
@@ -1308,7 +1541,7 @@ const docTemplate = `{
"required": true
},
{
- "description": "update app endpoint request",
+ "description": "Request body to update app endpoint",
"name": "body",
"in": "body",
"required": true,
@@ -1321,7 +1554,35 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "/organizations/{organizationId}/app-serve-apps/{appId}/exist": {
+ "get": {
+ "security": [
+ {
+ "JWT": []
+ }
+ ],
+ "description": "Get appServeApp by giving params",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "AppServeApps"
+ ],
+ "summary": "Get appServeApp",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "boolean"
}
}
}
@@ -1345,6 +1606,22 @@ const docTemplate = `{
"AppServeApps"
],
"summary": "Get latest task from appServeApp",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -1375,7 +1652,21 @@ const docTemplate = `{
"summary": "Rollback appServeApp",
"parameters": [
{
- "description": "rollback appserve request",
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Request body to rollback app",
"name": "object",
"in": "body",
"required": true,
@@ -1388,7 +1679,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1415,13 +1706,20 @@ const docTemplate = `{
"parameters": [
{
"type": "string",
- "description": "appId",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
"name": "appId",
"in": "path",
"required": true
},
{
- "description": "update app status request",
+ "description": "Request body to update app status",
"name": "body",
"in": "body",
"required": true,
@@ -1434,7 +1732,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1465,6 +1763,39 @@ const docTemplate = `{
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -2239,6 +2570,36 @@ const docTemplate = `{
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "combinedFilter",
+ "name": "combinedFilter",
+ "in": "query"
}
],
"responses": {
@@ -2587,6 +2948,39 @@ const docTemplate = `{
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -2924,6 +3318,41 @@ const docTemplate = `{
"StackTemplates"
],
"summary": "Get StackTemplates",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -3476,6 +3905,12 @@ const docTemplate = `{
"domain.ChartData": {
"type": "object",
"properties": {
+ "podCounts": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.PodCount"
+ }
+ },
"series": {
"type": "array",
"items": {
@@ -3532,6 +3967,9 @@ const docTemplate = `{
"createdAt": {
"type": "string"
},
+ "createdIAM": {
+ "type": "boolean"
+ },
"creator": {
"$ref": "#/definitions/domain.User"
},
@@ -3591,6 +4029,9 @@ const docTemplate = `{
"createdAt": {
"type": "string"
},
+ "createdIAM": {
+ "type": "boolean"
+ },
"creator": {
"$ref": "#/definitions/domain.SimpleUserResponse"
},
@@ -4324,6 +4765,20 @@ const docTemplate = `{
}
}
},
+ "domain.FilterResponse": {
+ "type": "object",
+ "properties": {
+ "column": {
+ "type": "string"
+ },
+ "values": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
"domain.FindIdRequest": {
"type": "object",
"required": [
@@ -4398,6 +4853,9 @@ const docTemplate = `{
"items": {
"$ref": "#/definitions/domain.AlertResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4417,6 +4875,9 @@ const docTemplate = `{
"items": {
"$ref": "#/definitions/domain.AppGroupResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4469,6 +4930,9 @@ const docTemplate = `{
"items": {
"$ref": "#/definitions/domain.CloudAccountResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4480,6 +4944,9 @@ const docTemplate = `{
"items": {
"$ref": "#/definitions/domain.ClusterResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4633,6 +5100,9 @@ const docTemplate = `{
"domain.GetStackTemplatesResponse": {
"type": "object",
"properties": {
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
+ },
"stackTemplates": {
"type": "array",
"items": {
@@ -4644,6 +5114,9 @@ const docTemplate = `{
"domain.GetStacksResponse": {
"type": "object",
"properties": {
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
+ },
"stacks": {
"type": "array",
"items": {
@@ -4861,6 +5334,46 @@ const docTemplate = `{
}
}
},
+ "domain.PaginationResponse": {
+ "type": "object",
+ "properties": {
+ "filters": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.FilterResponse"
+ }
+ },
+ "pageNumber": {
+ "type": "integer"
+ },
+ "pageSize": {
+ "type": "integer"
+ },
+ "sortColumn": {
+ "type": "string"
+ },
+ "sortOrder": {
+ "type": "string"
+ },
+ "totalPages": {
+ "type": "integer"
+ },
+ "totalRows": {
+ "type": "integer"
+ }
+ }
+ },
+ "domain.PodCount": {
+ "type": "object",
+ "properties": {
+ "day": {
+ "type": "integer"
+ },
+ "value": {
+ "type": "integer"
+ }
+ }
+ },
"domain.Role": {
"type": "object",
"properties": {
@@ -4904,6 +5417,9 @@ const docTemplate = `{
"clusters": {
"type": "integer"
},
+ "createdIAM": {
+ "type": "boolean"
+ },
"description": {
"type": "string"
},
diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json
index f35af473..cd91d143 100644
--- a/api/swagger/swagger.json
+++ b/api/swagger/swagger.json
@@ -40,6 +40,39 @@
"description": "clusterId",
"name": "clusterId",
"in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -481,6 +514,39 @@
"description": "organizationId",
"name": "organizationId",
"in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -657,6 +723,41 @@
"Organizations"
],
"summary": "Get organization list",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -848,6 +949,39 @@
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -1039,14 +1173,48 @@
"parameters": [
{
"type": "string",
- "description": "organization_Id",
- "name": "organization_Id",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "boolean",
+ "description": "Show all apps including deleted apps",
+ "name": "showAll",
"in": "query"
},
{
"type": "string",
- "description": "show_all",
- "name": "showAll",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
"in": "query"
}
],
@@ -1081,7 +1249,14 @@
"summary": "Install appServeApp",
"parameters": [
{
- "description": "create appserve request",
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Request body to create app",
"name": "object",
"in": "body",
"required": true,
@@ -1100,14 +1275,14 @@
}
}
},
- "/organizations/{organizationId}/app-serve-apps/app-id/exist": {
+ "/organizations/{organizationId}/app-serve-apps/count": {
"get": {
"security": [
{
"JWT": []
}
],
- "description": "Get appServeApp by giving params",
+ "description": "Get number of apps on given stack",
"consumes": [
"application/json"
],
@@ -1117,12 +1292,28 @@
"tags": [
"AppServeApps"
],
- "summary": "Get appServeApp",
+ "summary": "Get number of apps on given stack",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Stack ID",
+ "name": "stackId",
+ "in": "query",
+ "required": true
+ }
+ ],
"responses": {
"200": {
"description": "OK",
"schema": {
- "type": "boolean"
+ "type": "integer"
}
}
}
@@ -1149,7 +1340,7 @@
"parameters": [
{
"type": "string",
- "description": "organizationId",
+ "description": "Organization ID",
"name": "organizationId",
"in": "path",
"required": true
@@ -1190,6 +1381,22 @@
"AppServeApps"
],
"summary": "Get appServeApp",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -1218,7 +1425,21 @@
"summary": "Update appServeApp",
"parameters": [
{
- "description": "update appserve request",
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Request body to update app",
"name": "object",
"in": "body",
"required": true,
@@ -1231,7 +1452,7 @@
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1255,20 +1476,25 @@
"summary": "Uninstall appServeApp",
"parameters": [
{
- "description": "body",
- "name": "object",
- "in": "body",
- "required": true,
- "schema": {
- "type": "string"
- }
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1293,6 +1519,13 @@
],
"summary": "Update app endpoint",
"parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
{
"type": "string",
"description": "appId",
@@ -1301,7 +1534,7 @@
"required": true
},
{
- "description": "update app endpoint request",
+ "description": "Request body to update app endpoint",
"name": "body",
"in": "body",
"required": true,
@@ -1314,7 +1547,35 @@
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "/organizations/{organizationId}/app-serve-apps/{appId}/exist": {
+ "get": {
+ "security": [
+ {
+ "JWT": []
+ }
+ ],
+ "description": "Get appServeApp by giving params",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "AppServeApps"
+ ],
+ "summary": "Get appServeApp",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "boolean"
}
}
}
@@ -1338,6 +1599,22 @@
"AppServeApps"
],
"summary": "Get latest task from appServeApp",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -1368,7 +1645,21 @@
"summary": "Rollback appServeApp",
"parameters": [
{
- "description": "rollback appserve request",
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
+ "name": "appId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Request body to rollback app",
"name": "object",
"in": "body",
"required": true,
@@ -1381,7 +1672,7 @@
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1408,13 +1699,20 @@
"parameters": [
{
"type": "string",
- "description": "appId",
+ "description": "Organization ID",
+ "name": "organizationId",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "App ID",
"name": "appId",
"in": "path",
"required": true
},
{
- "description": "update app status request",
+ "description": "Request body to update app status",
"name": "body",
"in": "body",
"required": true,
@@ -1427,7 +1725,7 @@
"200": {
"description": "OK",
"schema": {
- "type": "object"
+ "type": "string"
}
}
}
@@ -1458,6 +1756,39 @@
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -2232,6 +2563,36 @@
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "combinedFilter",
+ "name": "combinedFilter",
+ "in": "query"
}
],
"responses": {
@@ -2580,6 +2941,39 @@
"name": "organizationId",
"in": "path",
"required": true
+ },
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
}
],
"responses": {
@@ -2917,6 +3311,41 @@
"StackTemplates"
],
"summary": "Get StackTemplates",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "pageSize",
+ "name": "limit",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "pageNumber",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortColumn",
+ "name": "soertColumn",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "sortOrder",
+ "name": "sortOrder",
+ "in": "query"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "filters",
+ "name": "filters",
+ "in": "query"
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -3469,6 +3898,12 @@
"domain.ChartData": {
"type": "object",
"properties": {
+ "podCounts": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.PodCount"
+ }
+ },
"series": {
"type": "array",
"items": {
@@ -3525,6 +3960,9 @@
"createdAt": {
"type": "string"
},
+ "createdIAM": {
+ "type": "boolean"
+ },
"creator": {
"$ref": "#/definitions/domain.User"
},
@@ -3584,6 +4022,9 @@
"createdAt": {
"type": "string"
},
+ "createdIAM": {
+ "type": "boolean"
+ },
"creator": {
"$ref": "#/definitions/domain.SimpleUserResponse"
},
@@ -4317,6 +4758,20 @@
}
}
},
+ "domain.FilterResponse": {
+ "type": "object",
+ "properties": {
+ "column": {
+ "type": "string"
+ },
+ "values": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
"domain.FindIdRequest": {
"type": "object",
"required": [
@@ -4391,6 +4846,9 @@
"items": {
"$ref": "#/definitions/domain.AlertResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4410,6 +4868,9 @@
"items": {
"$ref": "#/definitions/domain.AppGroupResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4462,6 +4923,9 @@
"items": {
"$ref": "#/definitions/domain.CloudAccountResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4473,6 +4937,9 @@
"items": {
"$ref": "#/definitions/domain.ClusterResponse"
}
+ },
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
}
}
},
@@ -4626,6 +5093,9 @@
"domain.GetStackTemplatesResponse": {
"type": "object",
"properties": {
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
+ },
"stackTemplates": {
"type": "array",
"items": {
@@ -4637,6 +5107,9 @@
"domain.GetStacksResponse": {
"type": "object",
"properties": {
+ "pagination": {
+ "$ref": "#/definitions/domain.PaginationResponse"
+ },
"stacks": {
"type": "array",
"items": {
@@ -4854,6 +5327,46 @@
}
}
},
+ "domain.PaginationResponse": {
+ "type": "object",
+ "properties": {
+ "filters": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.FilterResponse"
+ }
+ },
+ "pageNumber": {
+ "type": "integer"
+ },
+ "pageSize": {
+ "type": "integer"
+ },
+ "sortColumn": {
+ "type": "string"
+ },
+ "sortOrder": {
+ "type": "string"
+ },
+ "totalPages": {
+ "type": "integer"
+ },
+ "totalRows": {
+ "type": "integer"
+ }
+ }
+ },
+ "domain.PodCount": {
+ "type": "object",
+ "properties": {
+ "day": {
+ "type": "integer"
+ },
+ "value": {
+ "type": "integer"
+ }
+ }
+ },
"domain.Role": {
"type": "object",
"properties": {
@@ -4897,6 +5410,9 @@
"clusters": {
"type": "integer"
},
+ "createdIAM": {
+ "type": "boolean"
+ },
"description": {
"type": "string"
},
diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml
index 227552be..4227bbed 100644
--- a/api/swagger/swagger.yaml
+++ b/api/swagger/swagger.yaml
@@ -251,6 +251,10 @@ definitions:
type: object
domain.ChartData:
properties:
+ podCounts:
+ items:
+ $ref: '#/definitions/domain.PodCount'
+ type: array
series:
items:
$ref: '#/definitions/domain.Unit'
@@ -287,6 +291,8 @@ definitions:
type: integer
createdAt:
type: string
+ createdIAM:
+ type: boolean
creator:
$ref: '#/definitions/domain.User'
creatorId:
@@ -326,6 +332,8 @@ definitions:
type: integer
createdAt:
type: string
+ createdIAM:
+ type: boolean
creator:
$ref: '#/definitions/domain.SimpleUserResponse'
description:
@@ -824,6 +832,15 @@ definitions:
- accessKeyId
- secretAccessKey
type: object
+ domain.FilterResponse:
+ properties:
+ column:
+ type: string
+ values:
+ items:
+ type: string
+ type: array
+ type: object
domain.FindIdRequest:
properties:
code:
@@ -875,6 +892,8 @@ definitions:
items:
$ref: '#/definitions/domain.AlertResponse'
type: array
+ pagination:
+ $ref: '#/definitions/domain.PaginationResponse'
type: object
domain.GetAppGroupResponse:
properties:
@@ -887,6 +906,8 @@ definitions:
items:
$ref: '#/definitions/domain.AppGroupResponse'
type: array
+ pagination:
+ $ref: '#/definitions/domain.PaginationResponse'
type: object
domain.GetAppServeAppResponse:
properties:
@@ -920,6 +941,8 @@ definitions:
items:
$ref: '#/definitions/domain.CloudAccountResponse'
type: array
+ pagination:
+ $ref: '#/definitions/domain.PaginationResponse'
type: object
domain.GetClustersResponse:
properties:
@@ -927,6 +950,8 @@ definitions:
items:
$ref: '#/definitions/domain.ClusterResponse'
type: array
+ pagination:
+ $ref: '#/definitions/domain.PaginationResponse'
type: object
domain.GetDashboardChartResponse:
properties:
@@ -1024,6 +1049,8 @@ definitions:
type: object
domain.GetStackTemplatesResponse:
properties:
+ pagination:
+ $ref: '#/definitions/domain.PaginationResponse'
stackTemplates:
items:
$ref: '#/definitions/domain.StackTemplateResponse'
@@ -1031,6 +1058,8 @@ definitions:
type: object
domain.GetStacksResponse:
properties:
+ pagination:
+ $ref: '#/definitions/domain.PaginationResponse'
stacks:
items:
$ref: '#/definitions/domain.StackResponse'
@@ -1173,6 +1202,32 @@ definitions:
updatedAt:
type: string
type: object
+ domain.PaginationResponse:
+ properties:
+ filters:
+ items:
+ $ref: '#/definitions/domain.FilterResponse'
+ type: array
+ pageNumber:
+ type: integer
+ pageSize:
+ type: integer
+ sortColumn:
+ type: string
+ sortOrder:
+ type: string
+ totalPages:
+ type: integer
+ totalRows:
+ type: integer
+ type: object
+ domain.PodCount:
+ properties:
+ day:
+ type: integer
+ value:
+ type: integer
+ type: object
domain.Role:
properties:
createdAt:
@@ -1201,6 +1256,8 @@ definitions:
type: string
clusters:
type: integer
+ createdIAM:
+ type: boolean
description:
type: string
id:
@@ -1743,6 +1800,28 @@ paths:
in: query
name: clusterId
type: string
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -2009,6 +2088,28 @@ paths:
in: query
name: organizationId
type: string
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -2117,6 +2218,29 @@ paths:
consumes:
- application/json
description: Get organization list
+ parameters:
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -2238,6 +2362,28 @@ paths:
name: organizationId
required: true
type: string
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -2356,14 +2502,37 @@ paths:
- application/json
description: Get appServeApp list by giving params
parameters:
- - description: organization_Id
- in: query
- name: organization_Id
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
type: string
- - description: show_all
+ - description: Show all apps including deleted apps
in: query
name: showAll
+ type: boolean
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -2383,7 +2552,12 @@ paths:
- application/json
description: Install appServeApp
parameters:
- - description: create appserve request
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: Request body to create app
in: body
name: object
required: true
@@ -2407,19 +2581,23 @@ paths:
- application/json
description: Uninstall appServeApp
parameters:
- - description: body
- in: body
- name: object
+ - description: Organization ID
+ in: path
+ name: organizationId
required: true
- schema:
- type: string
+ type: string
+ - description: App ID
+ in: path
+ name: appId
+ required: true
+ type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
- type: object
+ type: string
security:
- JWT: []
summary: Uninstall appServeApp
@@ -2429,6 +2607,17 @@ paths:
consumes:
- application/json
description: Get appServeApp by giving params
+ parameters:
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: App ID
+ in: path
+ name: appId
+ required: true
+ type: string
produces:
- application/json
responses:
@@ -2446,7 +2635,17 @@ paths:
- application/json
description: Update appServeApp
parameters:
- - description: update appserve request
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: App ID
+ in: path
+ name: appId
+ required: true
+ type: string
+ - description: Request body to update app
in: body
name: object
required: true
@@ -2458,7 +2657,7 @@ paths:
"200":
description: OK
schema:
- type: object
+ type: string
security:
- JWT: []
summary: Update appServeApp
@@ -2470,12 +2669,17 @@ paths:
- application/json
description: Update app endpoint
parameters:
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
- description: appId
in: path
name: appId
required: true
type: string
- - description: update app endpoint request
+ - description: Request body to update app endpoint
in: body
name: body
required: true
@@ -2487,17 +2691,45 @@ paths:
"200":
description: OK
schema:
- type: object
+ type: string
security:
- JWT: []
summary: Update app endpoint
tags:
- AppServeApps
+ /organizations/{organizationId}/app-serve-apps/{appId}/exist:
+ get:
+ consumes:
+ - application/json
+ description: Get appServeApp by giving params
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ type: boolean
+ security:
+ - JWT: []
+ summary: Get appServeApp
+ tags:
+ - AppServeApps
/organizations/{organizationId}/app-serve-apps/{appId}/latest-task:
get:
consumes:
- application/json
description: Get latest task from appServeApp
+ parameters:
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: App ID
+ in: path
+ name: appId
+ required: true
+ type: string
produces:
- application/json
responses:
@@ -2516,7 +2748,17 @@ paths:
- application/json
description: Rollback appServeApp
parameters:
- - description: rollback appserve request
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: App ID
+ in: path
+ name: appId
+ required: true
+ type: string
+ - description: Request body to rollback app
in: body
name: object
required: true
@@ -2528,7 +2770,7 @@ paths:
"200":
description: OK
schema:
- type: object
+ type: string
security:
- JWT: []
summary: Rollback appServeApp
@@ -2540,12 +2782,17 @@ paths:
- application/json
description: Update app status
parameters:
- - description: appId
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: App ID
in: path
name: appId
required: true
type: string
- - description: update app status request
+ - description: Request body to update app status
in: body
name: body
required: true
@@ -2557,27 +2804,38 @@ paths:
"200":
description: OK
schema:
- type: object
+ type: string
security:
- JWT: []
summary: Update app status
tags:
- AppServeApps
- /organizations/{organizationId}/app-serve-apps/app-id/exist:
+ /organizations/{organizationId}/app-serve-apps/count:
get:
consumes:
- application/json
- description: Get appServeApp by giving params
+ description: Get number of apps on given stack
+ parameters:
+ - description: Organization ID
+ in: path
+ name: organizationId
+ required: true
+ type: string
+ - description: Stack ID
+ in: query
+ name: stackId
+ required: true
+ type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
- type: boolean
+ type: integer
security:
- JWT: []
- summary: Get appServeApp
+ summary: Get number of apps on given stack
tags:
- AppServeApps
/organizations/{organizationId}/app-serve-apps/name/{name}/existence:
@@ -2586,7 +2844,7 @@ paths:
- application/json
description: Check duplicate appServeAppName by giving params
parameters:
- - description: organizationId
+ - description: Organization ID
in: path
name: organizationId
required: true
@@ -2619,6 +2877,28 @@ paths:
name: organizationId
required: true
type: string
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -3110,6 +3390,26 @@ paths:
name: organizationId
required: true
type: string
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: combinedFilter
+ in: query
+ name: combinedFilter
+ type: string
produces:
- application/json
responses:
@@ -3332,6 +3632,28 @@ paths:
name: organizationId
required: true
type: string
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
@@ -3546,6 +3868,29 @@ paths:
consumes:
- application/json
description: Get StackTemplates
+ parameters:
+ - description: pageSize
+ in: query
+ name: limit
+ type: string
+ - description: pageNumber
+ in: query
+ name: page
+ type: string
+ - description: sortColumn
+ in: query
+ name: soertColumn
+ type: string
+ - description: sortOrder
+ in: query
+ name: sortOrder
+ type: string
+ - description: filters
+ in: query
+ items:
+ type: string
+ name: filters
+ type: array
produces:
- application/json
responses:
diff --git a/internal/aws/ses/contents/authcode.html b/internal/aws/ses/contents/authcode.html
new file mode 100644
index 00000000..09f79474
--- /dev/null
+++ b/internal/aws/ses/contents/authcode.html
@@ -0,0 +1,107 @@
+
+
+
+
+ 이메일인증 안내
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ 이메일 인증 안내
+ |
+
+
+ |
+
+
+ 안녕하세요. 항상 저희 TKS Cloud Service를 사랑해 주시고 성원해 주시는 고객님께 감사드립니다. |
+
+
+ |
+
+
+
+ 고객님께서 입력하신 이메일 주소 인증을 위해 아래 6자리 인증번호를
+ 화면에 입력해 주세요. |
+
+
+ |
+
+
+ 이메일 인증코드 |
+
+
+ |
+
+
+ {{.AuthCode}} |
+
+
+ |
+
+
+ |
+
+
+ 더욱 편리한 서비스를 제공하기 위해 항상 최선을 다하겠습니다. 감사합니다. |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+ 본 메일은 발신 전용 메일로, 회신 되지 않습니다. |
+ |
+ |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+ |
+ 우편번호: 04539 서울특별시중구을지로65 (을지로2가) SK T-타워 SK텔레콤㈜ 대표이사 : 유영상 COPYRIGHT SK TELECOM CO., LTD. ALL RIGHTS RESERVED. |
+ |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+
+
+
+
diff --git a/internal/aws/ses/contents/organization_creation.html b/internal/aws/ses/contents/organization_creation.html
new file mode 100644
index 00000000..988411d7
--- /dev/null
+++ b/internal/aws/ses/contents/organization_creation.html
@@ -0,0 +1,129 @@
+
+
+
+
+ 조직 생성
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ 조직 생성 안내
+ |
+
+
+ |
+
+
+ 안녕하세요. 항상 저희 TKS Cloud Service를 사랑해 주시고 성원해 주시는 고객님께 감사드립니다. |
+
+
+ |
+
+
+
+ 조직이 생성되었습니다.
+ 아래의 정보를 이용하여 로그인 해주시기 바랍니다. |
+
+
+ |
+
+
+ 로그인 정보 |
+
+
+ |
+
+
+
+ • 조직코드: {{.OrganizationId}}
+ • 아이디: {{.Id}}
+ • 비밀번호: {{.Password}}
+ |
+
+
+ |
+
+
+ 조직 정보 |
+
+
+ |
+
+
+
+ • 조직이름: {{.OrganizationName}}
+ • 관리자 이름: {{.AdminName}}
+ |
+
+
+
+ |
+
+
+ |
+
+
+ 더욱 편리한 서비스를 제공하기 위해 항상 최선을 다하겠습니다. 감사합니다. |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+ 본 메일은 발신 전용 메일로, 회신 되지 않습니다. |
+ |
+ |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+ |
+ 우편번호: 04539 서울특별시중구을지로65 (을지로2가) SK T-타워 SK텔레콤㈜ 대표이사 : 유영상 COPYRIGHT SK TELECOM CO., LTD. ALL RIGHTS RESERVED. |
+ |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+
+
+
+
diff --git a/internal/aws/ses/contents/temporary_password.html b/internal/aws/ses/contents/temporary_password.html
new file mode 100644
index 00000000..775f434a
--- /dev/null
+++ b/internal/aws/ses/contents/temporary_password.html
@@ -0,0 +1,107 @@
+
+
+
+
+ 임시 비밀번호 발급
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ 임시 비밀번호 발급 안내
+ |
+
+
+ |
+
+
+ 안녕하세요. 항상 저희 TKS Cloud Service를 사랑해 주시고 성원해 주시는 고객님께 감사드립니다. |
+
+
+ |
+
+
+
+ 임시 비밀번호가 발급되었습니다.
+ 로그인 후 비밀번호를 변경하여 사용해 주시기 바랍니다. |
+
+
+ |
+
+
+ 임시 비밀번호 |
+
+
+ |
+
+
+ {{.TemporaryPassword}} |
+
+
+ |
+
+
+ |
+
+
+ 더욱 편리한 서비스를 제공하기 위해 항상 최선을 다하겠습니다. 감사합니다. |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+ 본 메일은 발신 전용 메일로, 회신 되지 않습니다. |
+ |
+ |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+ |
+ 우편번호: 04539 서울특별시중구을지로65 (을지로2가) SK T-타워 SK텔레콤㈜ 대표이사 : 유영상 COPYRIGHT SK TELECOM CO., LTD. ALL RIGHTS RESERVED. |
+ |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+
+
+
+
diff --git a/internal/aws/ses/ses.go b/internal/aws/ses/ses.go
index f1cce3a4..f99bdc42 100644
--- a/internal/aws/ses/ses.go
+++ b/internal/aws/ses/ses.go
@@ -1,8 +1,11 @@
package ses
import (
+ "bytes"
"context"
+ "embed"
"fmt"
+ "html/template"
"os"
"github.com/aws/aws-sdk-go-v2/aws"
@@ -13,12 +16,13 @@ import (
"github.com/spf13/viper"
)
+//go:embed contents/*.html
+var templateFS embed.FS
+
var Client *awsSes.Client
const (
senderEmailAddress = "tks-dev@sktelecom.com"
-
- thanksContent = "TKS Cloud Service를 이용해 주셔서 감사합니다.\nTKS Cloud Service Team 드림"
)
func Initialize() error {
@@ -55,90 +59,118 @@ func Initialize() error {
return nil
}
func SendEmailForVerityIdentity(client *awsSes.Client, targetEmailAddress string, code string) error {
- subject := "[TKS][인증번호:" + code + "] – 요청하신 인증번호를 알려드립니다."
- body := "아래의 인증번호를 인증번호 입력창에 입력해 주세요.\n\n" +
- "인증번호: " + code + "\n\n" +
- thanksContent
+ subject := "[TKS] [인증번호:" + code + "] 인증번호가 발급되었습니다."
- input := &awsSes.SendEmailInput{
- Destination: &types.Destination{
- ToAddresses: []string{targetEmailAddress},
- },
- Message: &types.Message{
- Body: &types.Body{
- Text: &types.Content{
- Data: aws.String(body),
- },
- },
- Subject: &types.Content{
- Data: aws.String(subject),
- },
- },
- Source: aws.String(senderEmailAddress),
+ tmpl, err := template.ParseFS(templateFS, "contents/authcode.html")
+ if err != nil {
+ log.Errorf("failed to parse template, %v", err)
+ return err
}
- if _, err := client.SendEmail(context.Background(), input); err != nil {
- log.Errorf("failed to send email, %v", err)
+ type TemplateData struct {
+ AuthCode string
+ }
+
+ data := TemplateData{
+ AuthCode: code,
+ }
+
+ var tpl bytes.Buffer
+ if err := tmpl.Execute(&tpl, data); err != nil {
+ log.Errorf("failed to execute template, %v", err)
return err
}
+ body := tpl.String()
+
+ err = sendEmail(client, targetEmailAddress, subject, body)
+ if err != nil {
+ return err
+ }
return nil
}
func SendEmailForTemporaryPassword(client *awsSes.Client, targetEmailAddress string, randomPassword string) error {
- subject := "[TKS] 임시 비밀번호 발급"
- body := "임시 비밀번호가 발급되었습니다.\n" +
- "로그인 후 비밀번호를 변경하여 사용하십시오.\n\n" +
- "임시 비밀번호: " + randomPassword + "\n\n" +
- thanksContent
+ subject := "[TKS] 임시 비밀번호가 발급되었습니다."
- input := &awsSes.SendEmailInput{
- Destination: &types.Destination{
- ToAddresses: []string{targetEmailAddress},
- },
- Message: &types.Message{
- Body: &types.Body{
- Text: &types.Content{
- Data: aws.String(body),
- },
- },
- Subject: &types.Content{
- Data: aws.String(subject),
- },
- },
- Source: aws.String(senderEmailAddress),
+ tmpl, err := template.ParseFS(templateFS, "contents/temporary_password.html")
+ if err != nil {
+ log.Errorf("failed to parse template, %v", err)
+ return err
}
- if _, err := client.SendEmail(context.Background(), input); err != nil {
- log.Errorf("failed to send email, %v", err)
+ type TemplateData struct {
+ TemporaryPassword string
+ }
+
+ data := TemplateData{
+ TemporaryPassword: randomPassword,
+ }
+
+ var tpl bytes.Buffer
+ if err := tmpl.Execute(&tpl, data); err != nil {
+ log.Errorf("failed to execute template, %v", err)
return err
}
+ body := tpl.String()
+
+ err = sendEmail(client, targetEmailAddress, subject, body)
+ if err != nil {
+ return err
+ }
return nil
}
func SendEmailForGeneratingOrganization(client *awsSes.Client, organizationId string, organizationName string,
targetEmailAddress string, userAccountId string, randomPassword string) error {
subject := "[TKS] 조직이 생성되었습니다."
- body := "조직이 생성되었습니다. \n" +
- "조직코드: " + organizationId + "\n" +
- "이름: " + organizationName + "\n" +
- "관리자 아이디: " + userAccountId + "\n" +
- "관리자 이름: admin\n\n" +
- "아래 관리자 계정 정보로 로그인 후 사용하시기 바랍니다.\n" +
- "조직코드: " + organizationId + "\n" +
- "아이디: " + userAccountId + "\n" +
- "비밀번호: " + randomPassword + "\n\n" +
- thanksContent
+ tmpl, err := template.ParseFS(templateFS, "contents/organization_creation.html")
+ if err != nil {
+ log.Errorf("failed to parse template, %v", err)
+ return err
+ }
+
+ type TemplateData struct {
+ OrganizationId string
+ Id string
+ Password string
+ OrganizationName string
+ AdminName string
+ }
+
+ data := TemplateData{
+ OrganizationId: organizationId,
+ Id: userAccountId,
+ Password: randomPassword,
+ OrganizationName: organizationName,
+ AdminName: userAccountId,
+ }
+
+ var tpl bytes.Buffer
+ if err := tmpl.Execute(&tpl, data); err != nil {
+ log.Errorf("failed to execute template, %v", err)
+ return err
+ }
+ body := tpl.String()
+
+ err = sendEmail(client, targetEmailAddress, subject, body)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func sendEmail(client *awsSes.Client, targetEmailAddress string, subject string, htmlBody string) error {
input := &awsSes.SendEmailInput{
Destination: &types.Destination{
ToAddresses: []string{targetEmailAddress},
},
Message: &types.Message{
Body: &types.Body{
- Text: &types.Content{
- Data: aws.String(body),
+ Html: &types.Content{
+ Data: aws.String(htmlBody),
},
},
Subject: &types.Content{
diff --git a/internal/delivery/http/alert.go b/internal/delivery/http/alert.go
index 2a44f647..70b2208a 100644
--- a/internal/delivery/http/alert.go
+++ b/internal/delivery/http/alert.go
@@ -8,6 +8,7 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/openinfradev/tks-api/internal/helper"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -76,6 +77,11 @@ func (h *AlertHandler) CreateAlert(w http.ResponseWriter, r *http.Request) {
// @Accept json
// @Produce json
// @Param organizationId path string true "organizationId"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} domain.GetAlertsResponse
// @Router /organizations/{organizationId}/alerts [get]
// @Security JWT
@@ -87,7 +93,31 @@ func (h *AlertHandler) GetAlerts(w http.ResponseWriter, r *http.Request) {
return
}
- alerts, err := h.usecase.Fetch(r.Context(), organizationId)
+ urlParams := r.URL.Query()
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+ // convert status
+ for i, filter := range pg.GetFilters() {
+ if filter.Column == "status" {
+ for j, value := range filter.Values {
+ switch value {
+ case "CREATED":
+ pg.GetFilters()[i].Values[j] = "0"
+ case "INPROGRESS":
+ pg.GetFilters()[i].Values[j] = "1"
+ case "CLOSED":
+ pg.GetFilters()[i].Values[j] = "2"
+ case "ERROR":
+ pg.GetFilters()[i].Values[j] = "3"
+ }
+ }
+ }
+ }
+
+ alerts, err := h.usecase.Fetch(r.Context(), organizationId, pg)
if err != nil {
ErrorJSON(w, r, err)
return
@@ -112,6 +142,19 @@ func (h *AlertHandler) GetAlerts(w http.ResponseWriter, r *http.Request) {
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+ /*
+ outFilters := make([]domain.FilterResponse, len(pg.Filters))
+ for j, filter := range pg.Filters {
+ if err := domain.Map(filter, &outFilters[j]); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+ }
+ out.Pagination.Filters = outFilters
+ */
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/delivery/http/app-group.go b/internal/delivery/http/app-group.go
index 34d52ec3..84667143 100644
--- a/internal/delivery/http/app-group.go
+++ b/internal/delivery/http/app-group.go
@@ -6,7 +6,7 @@ import (
"github.com/gorilla/mux"
"github.com/openinfradev/tks-api/internal/helper"
- "github.com/openinfradev/tks-api/internal/middleware/auth/request"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -65,6 +65,11 @@ func (h *AppGroupHandler) CreateAppGroup(w http.ResponseWriter, r *http.Request)
// @Accept json
// @Produce json
// @Param clusterId query string false "clusterId"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} domain.GetAppGroupsResponse
// @Router /app-groups [get]
// @Security JWT
@@ -76,8 +81,13 @@ func (h *AppGroupHandler) GetAppGroups(w http.ResponseWriter, r *http.Request) {
ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid clusterId"), "C_INVALID_CLUSTER_ID", ""))
return
}
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
- appGroups, err := h.usecase.Fetch(r.Context(), domain.ClusterId(clusterId))
+ appGroups, err := h.usecase.Fetch(r.Context(), domain.ClusterId(clusterId), pg)
if err != nil {
ErrorJSON(w, r, err)
return
@@ -92,6 +102,10 @@ func (h *AppGroupHandler) GetAppGroups(w http.ResponseWriter, r *http.Request) {
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
@@ -142,12 +156,6 @@ func (h *AppGroupHandler) GetAppGroup(w http.ResponseWriter, r *http.Request) {
// @Router /app-groups [delete]
// @Security JWT
func (h *AppGroupHandler) DeleteAppGroup(w http.ResponseWriter, r *http.Request) {
- user, ok := request.UserFrom(r.Context())
- if !ok {
- ErrorJSON(w, r, httpErrors.NewUnauthorizedError(fmt.Errorf("Invalid token"), "A_INVALID_TOKEN", ""))
- return
- }
-
vars := mux.Vars(r)
strId, ok := vars["appGroupId"]
if !ok {
@@ -161,7 +169,7 @@ func (h *AppGroupHandler) DeleteAppGroup(w http.ResponseWriter, r *http.Request)
return
}
- err := h.usecase.Delete(r.Context(), user.GetOrganizationId(), appGroupId)
+ err := h.usecase.Delete(r.Context(), appGroupId)
if err != nil {
log.ErrorWithContext(r.Context(), "Failed to delete appGroup err : ", err)
ErrorJSON(w, r, err)
diff --git a/internal/delivery/http/app-serve-app.go b/internal/delivery/http/app-serve-app.go
index e805167c..f46b8c24 100644
--- a/internal/delivery/http/app-serve-app.go
+++ b/internal/delivery/http/app-serve-app.go
@@ -3,6 +3,7 @@ package http
import (
"fmt"
"net/http"
+ "regexp"
"strconv"
"strings"
"time"
@@ -10,6 +11,7 @@ import (
"github.com/gorilla/mux"
"github.com/openinfradev/tks-api/internal"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -79,7 +81,8 @@ func NewAppServeAppHandler(h usecase.IAppServeAppUsecase) *AppServeAppHandler {
// @Description Install appServeApp
// @Accept json
// @Produce json
-// @Param object body domain.CreateAppServeAppRequest true "create appserve request"
+// @Param organizationId path string true "Organization ID"
+// @Param object body domain.CreateAppServeAppRequest true "Request body to create app"
// @Success 200 {object} string
// @Router /organizations/{organizationId}/app-serve-apps [post]
// @Security JWT
@@ -127,6 +130,23 @@ func (h *AppServeAppHandler) CreateAppServeApp(w http.ResponseWriter, r *http.Re
app.AppServeAppTasks = append(app.AppServeAppTasks, task)
+ // Validate name param
+ re, _ := regexp.Compile("^[a-z][a-z0-9-]*$")
+ if !(re.MatchString(app.Name)) {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("error: name should consist of alphanumeric characters and hyphens only"), "", ""))
+ return
+ }
+
+ exist, err := h.usecase.IsAppServeAppNameExist(organizationId, app.Name)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewInternalServerError(err, "", ""))
+ return
+ }
+ if exist {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("error: name '%s' already exists.", app.Name), "", ""))
+ return
+ }
+
// Validate port param for springboot app
if app.AppType == "springboot" {
if task.Port == "" {
@@ -163,8 +183,13 @@ func (h *AppServeAppHandler) CreateAppServeApp(w http.ResponseWriter, r *http.Re
// @Description Get appServeApp list by giving params
// @Accept json
// @Produce json
-// @Param organization_Id query string false "organization_Id"
-// @Param showAll query string false "show_all"
+// @Param organizationId path string true "Organization ID"
+// @Param showAll query boolean false "Show all apps including deleted apps"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} []domain.AppServeApp
// @Router /organizations/{organizationId}/app-serve-apps [get]
// @Security JWT
@@ -190,8 +215,13 @@ func (h *AppServeAppHandler) GetAppServeApps(w http.ResponseWriter, r *http.Requ
ErrorJSON(w, r, err)
return
}
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
- apps, err := h.usecase.GetAppServeApps(organizationId, showAll)
+ apps, err := h.usecase.GetAppServeApps(organizationId, showAll, pg)
if err != nil {
log.ErrorWithContext(r.Context(), "Failed to get Failed to get app-serve-apps ", err)
ErrorJSON(w, r, err)
@@ -201,6 +231,10 @@ func (h *AppServeAppHandler) GetAppServeApps(w http.ResponseWriter, r *http.Requ
var out domain.GetAppServeAppsResponse
out.AppServeApps = apps
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
@@ -210,6 +244,8 @@ func (h *AppServeAppHandler) GetAppServeApps(w http.ResponseWriter, r *http.Requ
// @Description Get appServeApp by giving params
// @Accept json
// @Produce json
+// @Param organizationId path string true "Organization ID"
+// @Param appId path string true "App ID"
// @Success 200 {object} domain.GetAppServeAppResponse
// @Router /organizations/{organizationId}/app-serve-apps/{appId} [get]
// @Security JWT
@@ -264,6 +300,8 @@ func (h *AppServeAppHandler) GetAppServeApp(w http.ResponseWriter, r *http.Reque
// @Description Get latest task from appServeApp
// @Accept json
// @Produce json
+// @Param organizationId path string true "Organization ID"
+// @Param appId path string true "App ID"
// @Success 200 {object} domain.GetAppServeAppTaskResponse
// @Router /organizations/{organizationId}/app-serve-apps/{appId}/latest-task [get]
// @Security JWT
@@ -299,6 +337,43 @@ func (h *AppServeAppHandler) GetAppServeAppLatestTask(w http.ResponseWriter, r *
ResponseJSON(w, r, http.StatusOK, out)
}
+// GetNumOfAppsOnStack godoc
+// @Tags AppServeApps
+// @Summary Get number of apps on given stack
+// @Description Get number of apps on given stack
+// @Accept json
+// @Produce json
+// @Param organizationId path string true "Organization ID"
+// @Param stackId query string true "Stack ID"
+// @Success 200 {object} int64
+// @Router /organizations/{organizationId}/app-serve-apps/count [get]
+// @Security JWT
+func (h *AppServeAppHandler) GetNumOfAppsOnStack(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+
+ organizationId, ok := vars["organizationId"]
+ log.Debugf("organizationId = [%s]\n", organizationId)
+ if !ok {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("invalid organizationId"), "", ""))
+ return
+ }
+
+ urlParams := r.URL.Query()
+ stackId := urlParams.Get("stackId")
+ if stackId == "" {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("StackId must be provided."), "", ""))
+ }
+ fmt.Printf("stackId = [%s]\n", stackId)
+
+ numApps, err := h.usecase.GetNumOfAppsOnStack(organizationId, stackId)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewInternalServerError(err, "", ""))
+ return
+ }
+
+ ResponseJSON(w, r, http.StatusOK, numApps)
+}
+
func makeStages(app *domain.AppServeApp) []domain.StageResponse {
stages := make([]domain.StageResponse, 0)
@@ -389,22 +464,22 @@ func makeStage(app *domain.AppServeApp, pl string) domain.StageResponse {
} else if stage.Status == "PROMOTE_WAIT" && strategy == "blue-green" {
action := domain.ActionResponse{
- Name: "PROMOTE",
+ Name: "ABORT",
Uri: fmt.Sprintf(internal.API_PREFIX+internal.API_VERSION+
"/organizations/%v/app-serve-apps/%v", app.OrganizationId, app.ID),
Type: "API",
Method: "PUT",
- Body: map[string]string{"strategy": "blue-green", "promote": "true"},
+ Body: map[string]string{"strategy": "blue-green", "abort": "true"},
}
actions = append(actions, action)
action = domain.ActionResponse{
- Name: "ABORT",
+ Name: "PROMOTE",
Uri: fmt.Sprintf(internal.API_PREFIX+internal.API_VERSION+
"/organizations/%v/app-serve-apps/%v", app.OrganizationId, app.ID),
Type: "API",
Method: "PUT",
- Body: map[string]string{"strategy": "blue-green", "abort": "true"},
+ Body: map[string]string{"strategy": "blue-green", "promote": "true"},
}
actions = append(actions, action)
}
@@ -421,7 +496,7 @@ func makeStage(app *domain.AppServeApp, pl string) domain.StageResponse {
// @Accept json
// @Produce json
// @Success 200 {object} bool
-// @Router /organizations/{organizationId}/app-serve-apps/app-id/exist [get]
+// @Router /organizations/{organizationId}/app-serve-apps/{appId}/exist [get]
// @Security JWT
func (h *AppServeAppHandler) IsAppServeAppExist(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@@ -459,7 +534,7 @@ func (h *AppServeAppHandler) IsAppServeAppExist(w http.ResponseWriter, r *http.R
// @Description Check duplicate appServeAppName by giving params
// @Accept json
// @Produce json
-// @Param organizationId path string true "organizationId"
+// @Param organizationId path string true "Organization ID"
// @Param name path string true "name"
// @Success 200 {object} bool
// @Router /organizations/{organizationId}/app-serve-apps/name/{name}/existence [get]
@@ -498,8 +573,10 @@ func (h *AppServeAppHandler) IsAppServeAppNameExist(w http.ResponseWriter, r *ht
// @Description Update appServeApp
// @Accept json
// @Produce json
-// @Param object body domain.UpdateAppServeAppRequest true "update appserve request"
-// @Success 200 {object} object
+// @Param organizationId path string true "Organization ID"
+// @Param appId path string true "App ID"
+// @Param object body domain.UpdateAppServeAppRequest true "Request body to update app"
+// @Success 200 {object} string
// @Router /organizations/{organizationId}/app-serve-apps/{appId} [put]
// @Security JWT
func (h *AppServeAppHandler) UpdateAppServeApp(w http.ResponseWriter, r *http.Request) {
@@ -605,9 +682,10 @@ func (h *AppServeAppHandler) UpdateAppServeApp(w http.ResponseWriter, r *http.Re
// @Description Update app status
// @Accept json
// @Produce json
-// @Param appId path string true "appId"
-// @Param body body domain.UpdateAppServeAppStatusRequest true "update app status request"
-// @Success 200 {object} object
+// @Param organizationId path string true "Organization ID"
+// @Param appId path string true "App ID"
+// @Param body body domain.UpdateAppServeAppStatusRequest true "Request body to update app status"
+// @Success 200 {object} string
// @Router /organizations/{organizationId}/app-serve-apps/{appId}/status [patch]
// @Security JWT
func (h *AppServeAppHandler) UpdateAppServeAppStatus(w http.ResponseWriter, r *http.Request) {
@@ -647,9 +725,10 @@ func (h *AppServeAppHandler) UpdateAppServeAppStatus(w http.ResponseWriter, r *h
// @Description Update app endpoint
// @Accept json
// @Produce json
+// @Param organizationId path string true "Organization ID"
// @Param appId path string true "appId"
-// @Param body body domain.UpdateAppServeAppEndpointRequest true "update app endpoint request"
-// @Success 200 {object} object
+// @Param body body domain.UpdateAppServeAppEndpointRequest true "Request body to update app endpoint"
+// @Success 200 {object} string
// @Router /organizations/{organizationId}/app-serve-apps/{appId}/endpoint [patch]
// @Security JWT
func (h *AppServeAppHandler) UpdateAppServeAppEndpoint(w http.ResponseWriter, r *http.Request) {
@@ -694,8 +773,9 @@ func (h *AppServeAppHandler) UpdateAppServeAppEndpoint(w http.ResponseWriter, r
// @Description Uninstall appServeApp
// @Accept json
// @Produce json
-// @Param object body string true "body"
-// @Success 200 {object} object
+// @Param organizationId path string true "Organization ID"
+// @Param appId path string true "App ID"
+// @Success 200 {object} string
// @Router /organizations/{organizationId}/app-serve-apps/{appId} [delete]
// @Security JWT
func (h *AppServeAppHandler) DeleteAppServeApp(w http.ResponseWriter, r *http.Request) {
@@ -729,8 +809,10 @@ func (h *AppServeAppHandler) DeleteAppServeApp(w http.ResponseWriter, r *http.Re
// @Description Rollback appServeApp
// @Accept json
// @Produce json
-// @Param object body domain.RollbackAppServeAppRequest true "rollback appserve request"
-// @Success 200 {object} object
+// @Param organizationId path string true "Organization ID"
+// @Param appId path string true "App ID"
+// @Param object body domain.RollbackAppServeAppRequest true "Request body to rollback app"
+// @Success 200 {object} string
// @Router /organizations/{organizationId}/app-serve-apps/{appId}/rollback [post]
// @Security JWT
func (h *AppServeAppHandler) RollbackAppServeApp(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/delivery/http/cloud-account.go b/internal/delivery/http/cloud-account.go
index 732a765e..33c71eea 100644
--- a/internal/delivery/http/cloud-account.go
+++ b/internal/delivery/http/cloud-account.go
@@ -7,6 +7,7 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -75,6 +76,11 @@ func (h *CloudAccountHandler) CreateCloudAccount(w http.ResponseWriter, r *http.
// @Accept json
// @Produce json
// @Param organizationId path string true "organizationId"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} domain.GetCloudAccountsResponse
// @Router /organizations/{organizationId}/cloud-accounts [get]
// @Security JWT
@@ -86,7 +92,13 @@ func (h *CloudAccountHandler) GetCloudAccounts(w http.ResponseWriter, r *http.Re
return
}
- cloudAccounts, err := h.usecase.Fetch(r.Context(), organizationId)
+ urlParams := r.URL.Query()
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+ cloudAccounts, err := h.usecase.Fetch(r.Context(), organizationId, pg)
if err != nil {
ErrorJSON(w, r, err)
return
@@ -101,6 +113,10 @@ func (h *CloudAccountHandler) GetCloudAccounts(w http.ResponseWriter, r *http.Re
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/delivery/http/cluster.go b/internal/delivery/http/cluster.go
index 9f94ae76..6b0613d9 100644
--- a/internal/delivery/http/cluster.go
+++ b/internal/delivery/http/cluster.go
@@ -5,6 +5,7 @@ import (
"net/http"
"github.com/gorilla/mux"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -28,6 +29,11 @@ func NewClusterHandler(h usecase.IClusterUsecase) *ClusterHandler {
// @Accept json
// @Produce json
// @Param organizationId query string false "organizationId"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} domain.GetClustersResponse
// @Router /clusters [get]
// @Security JWT
@@ -35,7 +41,12 @@ func (h *ClusterHandler) GetClusters(w http.ResponseWriter, r *http.Request) {
urlParams := r.URL.Query()
organizationId := urlParams.Get("organizationId")
- clusters, err := h.usecase.Fetch(r.Context(), organizationId)
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+ clusters, err := h.usecase.Fetch(r.Context(), organizationId, pg)
if err != nil {
ErrorJSON(w, r, err)
return
@@ -50,6 +61,10 @@ func (h *ClusterHandler) GetClusters(w http.ResponseWriter, r *http.Request) {
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/delivery/http/handler.go b/internal/delivery/http/handler.go
index 1b39901d..46cdf4db 100644
--- a/internal/delivery/http/handler.go
+++ b/internal/delivery/http/handler.go
@@ -28,11 +28,12 @@ func init() {
}
func ErrorJSON(w http.ResponseWriter, r *http.Request, err error) {
+ log.ErrorfWithContext(r.Context(), "error is :%s(%T)", err.Error(), err)
errorResponse, status := httpErrors.ErrorResponse(err)
ResponseJSON(w, r, status, errorResponse)
}
-const MAX_LOG_LEN = 500
+const MAX_LOG_LEN = 1000
func ResponseJSON(w http.ResponseWriter, r *http.Request, httpStatus int, data interface{}) {
out := data
diff --git a/internal/delivery/http/organization.go b/internal/delivery/http/organization.go
index a9652fbe..12e420fb 100644
--- a/internal/delivery/http/organization.go
+++ b/internal/delivery/http/organization.go
@@ -6,6 +6,7 @@ import (
"github.com/gorilla/mux"
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -79,11 +80,23 @@ func (h *OrganizationHandler) CreateOrganization(w http.ResponseWriter, r *http.
// @Description Get organization list
// @Accept json
// @Produce json
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} []domain.ListOrganizationBody
// @Router /organizations [get]
// @Security JWT
func (h *OrganizationHandler) GetOrganizations(w http.ResponseWriter, r *http.Request) {
- organizations, err := h.usecase.Fetch()
+ urlParams := r.URL.Query()
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+
+ organizations, err := h.usecase.Fetch(pg)
if err != nil {
log.ErrorfWithContext(r.Context(), "error is :%s(%T)", err.Error(), err)
@@ -102,6 +115,10 @@ func (h *OrganizationHandler) GetOrganizations(w http.ResponseWriter, r *http.Re
log.InfoWithContext(r.Context(), organization)
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/delivery/http/stack-template.go b/internal/delivery/http/stack-template.go
index fa2a898d..56ebb222 100644
--- a/internal/delivery/http/stack-template.go
+++ b/internal/delivery/http/stack-template.go
@@ -7,6 +7,7 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -44,11 +45,23 @@ func (h *StackTemplateHandler) CreateStackTemplate(w http.ResponseWriter, r *htt
// @Description Get StackTemplates
// @Accept json
// @Produce json
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} domain.GetStackTemplatesResponse
// @Router /stack-templates [get]
// @Security JWT
func (h *StackTemplateHandler) GetStackTemplates(w http.ResponseWriter, r *http.Request) {
- stackTemplates, err := h.usecase.Fetch(r.Context())
+ urlParams := r.URL.Query()
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+
+ stackTemplates, err := h.usecase.Fetch(r.Context(), pg)
if err != nil {
ErrorJSON(w, r, err)
return
@@ -67,6 +80,10 @@ func (h *StackTemplateHandler) GetStackTemplates(w http.ResponseWriter, r *http.
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/delivery/http/stack.go b/internal/delivery/http/stack.go
index b03a5ca8..a85c52ed 100644
--- a/internal/delivery/http/stack.go
+++ b/internal/delivery/http/stack.go
@@ -5,6 +5,7 @@ import (
"net/http"
"github.com/gorilla/mux"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -76,6 +77,11 @@ func (h *StackHandler) CreateStack(w http.ResponseWriter, r *http.Request) {
// @Accept json
// @Produce json
// @Param organizationId path string true "organizationId"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param combinedFilter query string false "combinedFilter"
// @Success 200 {object} domain.GetStacksResponse
// @Router /organizations/{organizationId}/stacks [get]
// @Security JWT
@@ -86,7 +92,14 @@ func (h *StackHandler) GetStacks(w http.ResponseWriter, r *http.Request) {
ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid organizationId"), "C_INVALID_ORGANIZATION_ID", ""))
return
}
- stacks, err := h.usecase.Fetch(r.Context(), organizationId)
+
+ urlParams := r.URL.Query()
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+ stacks, err := h.usecase.Fetch(r.Context(), organizationId, pg)
if err != nil {
ErrorJSON(w, r, err)
return
@@ -101,6 +114,10 @@ func (h *StackHandler) GetStacks(w http.ResponseWriter, r *http.Request) {
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/delivery/http/user.go b/internal/delivery/http/user.go
index 36bcf2fb..53797942 100644
--- a/internal/delivery/http/user.go
+++ b/internal/delivery/http/user.go
@@ -7,6 +7,7 @@ import (
"github.com/gorilla/mux"
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/usecase"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -157,6 +158,11 @@ func (u UserHandler) Get(w http.ResponseWriter, r *http.Request) {
// @Accept json
// @Produce json
// @Param organizationId path string true "organizationId"
+// @Param limit query string false "pageSize"
+// @Param page query string false "pageNumber"
+// @Param soertColumn query string false "sortColumn"
+// @Param sortOrder query string false "sortOrder"
+// @Param filters query []string false "filters"
// @Success 200 {object} []domain.ListUserBody
// @Router /organizations/{organizationId}/users [get]
// @Security JWT
@@ -168,13 +174,14 @@ func (u UserHandler) List(w http.ResponseWriter, r *http.Request) {
return
}
- users, err := u.usecase.List(r.Context(), organizationId)
+ urlParams := r.URL.Query()
+ pg, err := pagination.NewPagination(&urlParams)
+ if err != nil {
+ ErrorJSON(w, r, httpErrors.NewBadRequestError(err, "", ""))
+ return
+ }
+ users, err := u.usecase.ListWithPagination(r.Context(), organizationId, pg)
if err != nil {
- if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound {
- ResponseJSON(w, r, http.StatusNoContent, domain.ListUserResponse{})
- return
- }
-
log.ErrorfWithContext(r.Context(), "error is :%s(%T)", err.Error(), err)
ErrorJSON(w, r, err)
return
@@ -188,6 +195,10 @@ func (u UserHandler) List(w http.ResponseWriter, r *http.Request) {
}
}
+ if err := domain.Map(*pg, &out.Pagination); err != nil {
+ log.InfoWithContext(r.Context(), err)
+ }
+
ResponseJSON(w, r, http.StatusOK, out)
}
diff --git a/internal/helper/util.go b/internal/helper/util.go
index 4da8fa44..c6e92f2a 100644
--- a/internal/helper/util.go
+++ b/internal/helper/util.go
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"math/big"
+ "regexp"
"strconv"
"strings"
"time"
@@ -70,3 +71,12 @@ func SplitAddress(url string) (address string, port int) {
return
}
+
+var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
+var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
+
+func ToSnakeCase(str string) string {
+ snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
+ snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
+ return strings.ToLower(snake)
+}
diff --git a/internal/keycloak/keycloak.go b/internal/keycloak/keycloak.go
index 6393e429..45976edb 100644
--- a/internal/keycloak/keycloak.go
+++ b/internal/keycloak/keycloak.go
@@ -58,7 +58,7 @@ func (k *Keycloak) LoginAdmin(accountId string, password string) (*domain.User,
func (k *Keycloak) Login(accountId string, password string, organizationId string) (*domain.User, error) {
ctx := context.Background()
- JWTToken, err := k.client.Login(ctx, DefaultClientID, DefaultClientSecret, organizationId, accountId, password)
+ JWTToken, err := k.client.Login(ctx, DefaultClientID, k.config.ClientSecret, organizationId, accountId, password)
if err != nil {
log.Error(err)
return nil, err
@@ -109,7 +109,7 @@ func (k *Keycloak) InitializeKeycloak() error {
var redirectURIs []string
redirectURIs = append(redirectURIs, viper.GetString("external-address")+"/*")
- tksClient, err := k.ensureClient(ctx, token, DefaultMasterRealm, DefaultClientID, DefaultClientSecret, &redirectURIs)
+ tksClient, err := k.ensureClient(ctx, token, DefaultMasterRealm, DefaultClientID, k.config.ClientSecret, &redirectURIs)
if err != nil {
log.Fatal(err)
return err
@@ -122,7 +122,7 @@ func (k *Keycloak) InitializeKeycloak() error {
}
}
- adminCliClient, err := k.ensureClient(ctx, token, DefaultMasterRealm, AdminCliClientID, DefaultClientSecret, nil)
+ adminCliClient, err := k.ensureClient(ctx, token, DefaultMasterRealm, AdminCliClientID, k.config.ClientSecret, nil)
if err != nil {
log.Fatal(err)
return err
@@ -139,7 +139,7 @@ func (k *Keycloak) InitializeKeycloak() error {
_ = getRefreshTokenExpiredDuration(k.adminCliToken)
go func() {
for {
- if token, err := k.client.RefreshToken(context.Background(), k.adminCliToken.RefreshToken, AdminCliClientID, DefaultClientSecret, DefaultMasterRealm); err != nil {
+ if token, err := k.client.RefreshToken(context.Background(), k.adminCliToken.RefreshToken, AdminCliClientID, k.config.ClientSecret, DefaultMasterRealm); err != nil {
log.Errorf("[Refresh]error is :%s(%T)", err.Error(), err)
log.Info("[Do Keycloak Admin CLI Login]")
k.adminCliToken, err = k.client.LoginAdmin(ctx, k.config.AdminId, k.config.AdminPassword, DefaultMasterRealm)
@@ -168,7 +168,7 @@ func (k *Keycloak) CreateRealm(organizationId string) (string, error) {
var redirectURIs []string
redirectURIs = append(redirectURIs, viper.GetString("external-address")+"/*")
- clientUUID, err := k.createDefaultClient(context.Background(), token.AccessToken, organizationId, DefaultClientID, DefaultClientSecret, &redirectURIs)
+ clientUUID, err := k.createDefaultClient(context.Background(), token.AccessToken, organizationId, DefaultClientID, k.config.ClientSecret, &redirectURIs)
if err != nil {
log.Error(err, "createDefaultClient")
return "", err
@@ -366,7 +366,7 @@ func (k *Keycloak) DeleteUser(organizationId string, userAccountId string) error
func (k *Keycloak) VerifyAccessToken(token string, organizationId string) error {
ctx := context.Background()
- rptResult, err := k.client.RetrospectToken(ctx, token, DefaultClientID, DefaultClientSecret, organizationId)
+ rptResult, err := k.client.RetrospectToken(ctx, token, DefaultClientID, k.config.ClientSecret, organizationId)
if err != nil {
return err
}
diff --git a/internal/pagination/pagination.go b/internal/pagination/pagination.go
new file mode 100644
index 00000000..40ed71ff
--- /dev/null
+++ b/internal/pagination/pagination.go
@@ -0,0 +1,137 @@
+package pagination
+
+import (
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/openinfradev/tks-api/internal/helper"
+)
+
+const SORT_COLUMN = "sortColumn"
+const SORT_ORDER = "sortOrder"
+const PAGE_NUMBER = "pageNumber"
+const PAGE_SIZE = "pageSize"
+const COMBINED_FILTER = "combinedFilter"
+
+type Pagination struct {
+ Limit int
+ Page int
+ SortColumn string
+ SortOrder string
+ Filters []Filter
+ CombinedFilter CombinedFilter
+ TotalRows int64
+ TotalPages int
+}
+
+type Filter struct {
+ Column string
+ Values []string
+}
+
+type CombinedFilter struct {
+ Columns []string
+ Value string
+}
+
+var DEFAULT_LIMIT = 10
+var MAX_LIMIT = 1000
+
+func (p *Pagination) GetOffset() int {
+ return (p.GetPage() - 1) * p.GetLimit()
+}
+
+func (p *Pagination) GetLimit() int {
+ if p.Limit == 0 {
+ p.Limit = DEFAULT_LIMIT
+ }
+ return p.Limit
+}
+
+func (p *Pagination) GetPage() int {
+ if p.Page == 0 {
+ p.Page = 1
+ }
+ return p.Page
+}
+
+func (p *Pagination) GetSortColumn() string {
+ return p.SortColumn
+}
+
+func (p *Pagination) GetSortOrder() string {
+ return p.SortOrder
+}
+
+func (p *Pagination) GetFilters() []Filter {
+ return p.Filters
+}
+
+func NewPagination(urlParams *url.Values) (*Pagination, error) {
+ pg := NewDefaultPagination()
+
+ for key, value := range *urlParams {
+ switch key {
+ case SORT_COLUMN:
+ if value[0] == "" {
+ pg.SortColumn = "created_at"
+ } else {
+ pg.SortColumn = value[0]
+ }
+ case SORT_ORDER:
+ if value[0] == "" {
+ pg.SortOrder = "ASC"
+ } else {
+ pg.SortOrder = value[0]
+ }
+ case PAGE_NUMBER:
+ if value[0] == "" {
+ pg.Page = 1
+ } else {
+ pg.Page, _ = strconv.Atoi(value[0])
+ }
+ case PAGE_SIZE:
+ if value[0] == "" {
+ pg.Page = DEFAULT_LIMIT
+ } else {
+ if limitNum, err := strconv.Atoi(value[0]); err == nil {
+ pg.Limit = limitNum
+ }
+ }
+ case COMBINED_FILTER:
+ if len(value[0]) > 0 {
+ //"combinedFilter=key1,key2:value"
+ filterArray := strings.Split(value[0], ":")
+ if len(filterArray) == 2 {
+ keys := strings.Split(helper.ToSnakeCase(strings.Replace(filterArray[0], "[]", "", -1)), ",")
+ value := filterArray[1]
+
+ pg.CombinedFilter = CombinedFilter{
+ Columns: keys,
+ Value: value,
+ }
+ } else {
+ return nil, fmt.Errorf("Invalid query string : combinedFilter ")
+ }
+ }
+ default:
+ pg.Filters = append(pg.Filters, Filter{
+ Column: helper.ToSnakeCase(strings.Replace(key, "[]", "", -1)),
+ Values: value,
+ })
+ }
+ }
+
+ return pg, nil
+}
+
+func NewDefaultPagination() *Pagination {
+ return &Pagination{
+ SortColumn: "created_at",
+ SortOrder: "ASC",
+ Page: 1,
+ Limit: MAX_LIMIT,
+ }
+}
diff --git a/internal/repository/alert.go b/internal/repository/alert.go
index ad64b983..e82e1999 100644
--- a/internal/repository/alert.go
+++ b/internal/repository/alert.go
@@ -1,6 +1,8 @@
package repository
import (
+ "fmt"
+ "math"
"time"
"github.com/google/uuid"
@@ -8,6 +10,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
)
@@ -15,7 +18,7 @@ import (
type IAlertRepository interface {
Get(alertId uuid.UUID) (domain.Alert, error)
GetByName(organizationId string, name string) (domain.Alert, error)
- Fetch(organizationId string) ([]domain.Alert, error)
+ Fetch(organizationId string, pg *pagination.Pagination) ([]domain.Alert, error)
FetchPodRestart(organizationId string, start time.Time, end time.Time) ([]domain.Alert, error)
Create(dto domain.Alert) (alertId uuid.UUID, err error)
Update(dto domain.Alert) (err error)
@@ -54,6 +57,7 @@ type Alert struct {
Summary string
AlertActions []AlertAction `gorm:"foreignKey:AlertId"`
RawData datatypes.JSON
+ Status domain.AlertActionStatus `gorm:"index"`
}
func (c *Alert) BeforeCreate(tx *gorm.DB) (err error) {
@@ -99,14 +103,27 @@ func (r *AlertRepository) GetByName(organizationId string, name string) (out dom
return
}
-func (r *AlertRepository) Fetch(organizationId string) (out []domain.Alert, err error) {
+func (r *AlertRepository) Fetch(organizationId string, pg *pagination.Pagination) (out []domain.Alert, err error) {
var alerts []Alert
- res := r.db.Preload("AlertActions", func(db *gorm.DB) *gorm.DB {
- return db.Order("created_at ASC")
- }).Preload("AlertActions.Taker").
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+
+ filterFunc := CombinedGormFilter("alerts", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&Alert{}).
+ Preload("AlertActions", func(db *gorm.DB) *gorm.DB {
+ return db.Order("created_at ASC")
+ }).Preload("AlertActions.Taker").
Preload("Cluster", "status = 2").
Preload("Organization").
- Order("created_at desc").Limit(1000).Find(&alerts, "organization_id = ?", organizationId)
+ Joins("join clusters on clusters.id = alerts.cluster_id AND clusters.status = 2").
+ Where("alerts.organization_id = ?", organizationId))
+
+ db.Count(&pg.TotalRows)
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&alerts)
if res.Error != nil {
return nil, res.Error
}
@@ -146,6 +163,7 @@ func (r *AlertRepository) Create(dto domain.Alert) (alertId uuid.UUID, err error
CheckPoint: dto.CheckPoint,
Summary: dto.Summary,
RawData: dto.RawData,
+ Status: domain.AlertActionStatus_CREATED,
}
res := r.db.Create(&alert)
if res.Error != nil {
@@ -183,6 +201,13 @@ func (r *AlertRepository) CreateAlertAction(dto domain.AlertAction) (alertAction
if res.Error != nil {
return uuid.Nil, res.Error
}
+ res = r.db.Model(&Alert{}).
+ Where("id = ?", dto.AlertId).
+ Update("status", dto.Status)
+ if res.Error != nil {
+ return uuid.Nil, res.Error
+ }
+
return alert.ID, nil
}
@@ -208,6 +233,7 @@ func reflectAlert(alert Alert) domain.Alert {
Summary: alert.Summary,
AlertActions: outAlertActions,
RawData: alert.RawData,
+ Status: alert.Status,
CreatedAt: alert.CreatedAt,
UpdatedAt: alert.UpdatedAt,
}
diff --git a/internal/repository/app-group.go b/internal/repository/app-group.go
index 9656e212..46f63d9a 100644
--- a/internal/repository/app-group.go
+++ b/internal/repository/app-group.go
@@ -2,19 +2,21 @@ package repository
import (
"fmt"
+ "math"
"github.com/google/uuid"
"gorm.io/datatypes"
"gorm.io/gorm"
"github.com/openinfradev/tks-api/internal/helper"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/log"
)
// Interfaces
type IAppGroupRepository interface {
- Fetch(clusterId domain.ClusterId) (res []domain.AppGroup, err error)
+ Fetch(clusterId domain.ClusterId, pg *pagination.Pagination) (res []domain.AppGroup, err error)
Get(id domain.AppGroupId) (domain.AppGroup, error)
Create(dto domain.AppGroup) (id domain.AppGroupId, err error)
Update(dto domain.AppGroup) (err error)
@@ -74,14 +76,27 @@ func (c *Application) BeforeCreate(tx *gorm.DB) (err error) {
}
// Logics
-func (r *AppGroupRepository) Fetch(clusterId domain.ClusterId) (out []domain.AppGroup, err error) {
+func (r *AppGroupRepository) Fetch(clusterId domain.ClusterId, pg *pagination.Pagination) (out []domain.AppGroup, err error) {
var appGroups []AppGroup
- out = []domain.AppGroup{}
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+
+ filterFunc := CombinedGormFilter("app_groups", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&AppGroup{}).
+ Where("cluster_id = ?", clusterId))
+ db.Count(&pg.TotalRows)
- res := r.db.Find(&appGroups, "cluster_id = ?", clusterId)
+ r.db.Model(&AppGroup{}).
+ Where("cluster_id = ?", clusterId).Where("id").Where("app_groups.status").Where("app_groups.deleted")
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&appGroups)
if res.Error != nil {
return nil, res.Error
}
+
for _, appGroup := range appGroups {
outAppGroup := reflectAppGroup(appGroup)
out = append(out, outAppGroup)
diff --git a/internal/repository/app-serve-app.go b/internal/repository/app-serve-app.go
index 2ed1643d..d27adfb6 100644
--- a/internal/repository/app-serve-app.go
+++ b/internal/repository/app-serve-app.go
@@ -2,19 +2,21 @@ package repository
import (
"fmt"
+ "math"
"time"
+ "github.com/openinfradev/tks-api/internal/pagination"
+ "github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/log"
"gorm.io/gorm"
-
- "github.com/openinfradev/tks-api/pkg/domain"
)
type IAppServeAppRepository interface {
CreateAppServeApp(app *domain.AppServeApp) (appId string, taskId string, err error)
- GetAppServeApps(organizationId string, showAll bool) ([]domain.AppServeApp, error)
+ GetAppServeApps(organizationId string, showAll bool, pg *pagination.Pagination) ([]domain.AppServeApp, error)
GetAppServeAppById(appId string) (*domain.AppServeApp, error)
GetAppServeAppLatestTask(appId string) (*domain.AppServeAppTask, error)
+ GetNumOfAppsOnStack(organizationId string, clusterId string) (int64, error)
IsAppServeAppExist(appId string) (int64, error)
IsAppServeAppNameExist(orgId string, appName string) (int64, error)
CreateTask(task *domain.AppServeAppTask) (taskId string, err error)
@@ -55,15 +57,20 @@ func (r *AppServeAppRepository) CreateTask(
return task.ID, nil
}
-func (r *AppServeAppRepository) GetAppServeApps(organizationId string, showAll bool) ([]domain.AppServeApp, error) {
- var apps []domain.AppServeApp
+func (r *AppServeAppRepository) GetAppServeApps(organizationId string, showAll bool, pg *pagination.Pagination) (apps []domain.AppServeApp, err error) {
var clusters []Cluster
-
- queryStr := fmt.Sprintf("organization_id = '%s' AND status <> 'DELETE_SUCCESS'", organizationId)
- if showAll {
- queryStr = fmt.Sprintf("organization_id = '%s'", organizationId)
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
}
- res := r.db.Order("created_at desc").Find(&apps, queryStr)
+
+ filterFunc := CombinedGormFilter("app_serve_apps", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&domain.AppServeApp{}).
+ Where("app_serve_apps.organization_id = ? AND status <> 'DELETE_SUCCESS'", organizationId))
+ db.Count(&pg.TotalRows)
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&apps)
if res.Error != nil {
return nil, fmt.Errorf("error while finding appServeApps with organizationId: %s", organizationId)
}
@@ -74,8 +81,8 @@ func (r *AppServeAppRepository) GetAppServeApps(organizationId string, showAll b
}
// Add cluster names to apps list
- queryStr = fmt.Sprintf("organization_id = '%s' AND status <> '%d'", organizationId, domain.ClusterStatus_DELETED)
- res = r.db.Order("created_at desc").Find(&clusters, queryStr)
+ queryStr := fmt.Sprintf("organization_id = '%s' AND status <> '%d'", organizationId, domain.ClusterStatus_DELETED)
+ res = r.db.Find(&clusters, queryStr)
if res.Error != nil {
return nil, fmt.Errorf("error while fetching clusters with organizationId: %s", organizationId)
}
@@ -89,7 +96,7 @@ func (r *AppServeAppRepository) GetAppServeApps(organizationId string, showAll b
}
}
- return apps, nil
+ return
}
func (r *AppServeAppRepository) GetAppServeAppById(appId string) (*domain.AppServeApp, error) {
@@ -113,7 +120,6 @@ func (r *AppServeAppRepository) GetAppServeAppById(appId string) (*domain.AppSer
// Add cluster name to app object
r.db.Select("name").Where("id = ?", app.TargetClusterId).First(&cluster)
app.TargetClusterName = cluster.Name
- log.Infof("App struct with cluster name:\n%+v", app)
return &app, nil
}
@@ -133,6 +139,18 @@ func (r *AppServeAppRepository) GetAppServeAppLatestTask(appId string) (*domain.
return &task, nil
}
+func (r *AppServeAppRepository) GetNumOfAppsOnStack(organizationId string, clusterId string) (int64, error) {
+ var apps []domain.AppServeApp
+
+ queryStr := fmt.Sprintf("organization_id = '%s' AND target_cluster_id = '%s' AND status <> 'DELETE_SUCCESS'", organizationId, clusterId)
+ res := r.db.Find(&apps, queryStr)
+ if res.Error != nil {
+ return -1, fmt.Errorf("Error while finding appServeApps with organizationId: %s", organizationId)
+ }
+
+ return res.RowsAffected, nil
+}
+
func (r *AppServeAppRepository) IsAppServeAppExist(appId string) (int64, error) {
var result int64
diff --git a/internal/repository/cloud-account.go b/internal/repository/cloud-account.go
index 72713761..95e4b89c 100644
--- a/internal/repository/cloud-account.go
+++ b/internal/repository/cloud-account.go
@@ -2,11 +2,13 @@ package repository
import (
"fmt"
+ "math"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
)
@@ -15,7 +17,7 @@ type ICloudAccountRepository interface {
Get(cloudAccountId uuid.UUID) (domain.CloudAccount, error)
GetByName(organizationId string, name string) (domain.CloudAccount, error)
GetByAwsAccountId(awsAccountId string) (domain.CloudAccount, error)
- Fetch(organizationId string) ([]domain.CloudAccount, error)
+ Fetch(organizationId string, pg *pagination.Pagination) ([]domain.CloudAccount, error)
Create(dto domain.CloudAccount) (cloudAccountId uuid.UUID, err error)
Update(dto domain.CloudAccount) (err error)
Delete(cloudAccountId uuid.UUID) (err error)
@@ -39,14 +41,15 @@ type CloudAccount struct {
ID uuid.UUID `gorm:"primarykey"`
OrganizationId string
Organization Organization `gorm:"foreignKey:OrganizationId"`
- Name string
- Description string
+ Name string `gorm:"index"`
+ Description string `gorm:"index"`
Resource string
CloudService string
WorkflowId string
Status domain.CloudAccountStatus
StatusDesc string
AwsAccountId string
+ CreatedIAM bool
CreatorId *uuid.UUID `gorm:"type:uuid"`
Creator User `gorm:"foreignKey:CreatorId"`
UpdatorId *uuid.UUID `gorm:"type:uuid"`
@@ -82,7 +85,7 @@ func (r *CloudAccountRepository) GetByName(organizationId string, name string) (
func (r *CloudAccountRepository) GetByAwsAccountId(awsAccountId string) (out domain.CloudAccount, err error) {
var cloudAccount CloudAccount
- res := r.db.Preload(clause.Associations).First(&cloudAccount, "aws_account_id = ?", awsAccountId)
+ res := r.db.Preload(clause.Associations).First(&cloudAccount, "aws_account_id = ? AND status != ?", awsAccountId, domain.CloudAccountStatus_DELETED)
if res.Error != nil {
return domain.CloudAccount{}, res.Error
@@ -91,9 +94,20 @@ func (r *CloudAccountRepository) GetByAwsAccountId(awsAccountId string) (out dom
return
}
-func (r *CloudAccountRepository) Fetch(organizationId string) (out []domain.CloudAccount, err error) {
+func (r *CloudAccountRepository) Fetch(organizationId string, pg *pagination.Pagination) (out []domain.CloudAccount, err error) {
var cloudAccounts []CloudAccount
- res := r.db.Preload(clause.Associations).Find(&cloudAccounts, "organization_id = ? AND status != ?", organizationId, domain.CloudAccountStatus_DELETED)
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+ filterFunc := CombinedGormFilter("cloud_accounts", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&CloudAccount{}).
+ Preload(clause.Associations).
+ Where("organization_id = ? AND status != ?", organizationId, domain.CloudAccountStatus_DELETED))
+ db.Count(&pg.TotalRows)
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&cloudAccounts)
if res.Error != nil {
return nil, res.Error
}
@@ -112,6 +126,7 @@ func (r *CloudAccountRepository) Create(dto domain.CloudAccount) (cloudAccountId
CloudService: dto.CloudService,
Resource: dto.Resource,
AwsAccountId: dto.AwsAccountId,
+ CreatedIAM: false,
Status: domain.CloudAccountStatus_PENDING,
CreatorId: &dto.CreatorId}
res := r.db.Create(&cloudAccount)
@@ -162,6 +177,7 @@ func reflectCloudAccount(cloudAccount CloudAccount) domain.CloudAccount {
Status: cloudAccount.Status,
StatusDesc: cloudAccount.StatusDesc,
AwsAccountId: cloudAccount.AwsAccountId,
+ CreatedIAM: cloudAccount.CreatedIAM,
Creator: reflectSimpleUser(cloudAccount.Creator),
Updator: reflectSimpleUser(cloudAccount.Updator),
CreatedAt: cloudAccount.CreatedAt,
diff --git a/internal/repository/cluster.go b/internal/repository/cluster.go
index 98221bb3..80ee8898 100644
--- a/internal/repository/cluster.go
+++ b/internal/repository/cluster.go
@@ -2,12 +2,14 @@ package repository
import (
"fmt"
+ "math"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/openinfradev/tks-api/internal/helper"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/log"
)
@@ -15,9 +17,9 @@ import (
// Interfaces
type IClusterRepository interface {
WithTrx(*gorm.DB) IClusterRepository
- Fetch() (res []domain.Cluster, err error)
- FetchByOrganizationId(organizationId string) (res []domain.Cluster, err error)
- FetchByCloudAccountId(cloudAccountId uuid.UUID) (res []domain.Cluster, err error)
+ Fetch(pg *pagination.Pagination) (res []domain.Cluster, err error)
+ FetchByCloudAccountId(cloudAccountId uuid.UUID, pg *pagination.Pagination) (res []domain.Cluster, err error)
+ FetchByOrganizationId(organizationId string, pg *pagination.Pagination) (res []domain.Cluster, err error)
Get(id domain.ClusterId) (domain.Cluster, error)
GetByName(organizationId string, name string) (domain.Cluster, error)
Create(dto domain.Cluster) (clusterId domain.ClusterId, err error)
@@ -44,10 +46,10 @@ type Cluster struct {
gorm.Model
ID domain.ClusterId `gorm:"primarykey"`
- Name string
+ Name string `gorm:"index"`
OrganizationId string
Organization Organization `gorm:"foreignKey:OrganizationId"`
- Description string
+ Description string `gorm:"index"`
WorkflowId string
Status domain.ClusterStatus
StatusDesc string
@@ -82,9 +84,19 @@ func (r *ClusterRepository) WithTrx(trxHandle *gorm.DB) IClusterRepository {
return r
}
-func (r *ClusterRepository) Fetch() (out []domain.Cluster, err error) {
+func (r *ClusterRepository) Fetch(pg *pagination.Pagination) (out []domain.Cluster, err error) {
var clusters []Cluster
- res := r.db.Preload(clause.Associations).Find(&clusters)
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+ filterFunc := CombinedGormFilter("clusters", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&Cluster{}))
+
+ db.Count(&pg.TotalRows)
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&clusters)
if res.Error != nil {
return nil, res.Error
}
@@ -92,48 +104,58 @@ func (r *ClusterRepository) Fetch() (out []domain.Cluster, err error) {
outCluster := reflectCluster(cluster)
out = append(out, outCluster)
}
- return out, nil
+ return
}
-// [TODO] Need refactoring about filters and pagination
-func (r *ClusterRepository) FetchByOrganizationId(organizationId string) (out []domain.Cluster, err error) {
+func (r *ClusterRepository) FetchByOrganizationId(organizationId string, pg *pagination.Pagination) (out []domain.Cluster, err error) {
var clusters []Cluster
- res := r.db.Preload(clause.Associations).Order("updated_at desc, created_at desc").Find(&clusters, "organization_id = ? AND status != ?", organizationId, domain.ClusterStatus_DELETED)
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+ pg.SortColumn = "updated_at"
+ pg.SortOrder = "DESC"
+ filterFunc := CombinedGormFilter("clusters", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&Cluster{}).Preload(clause.Associations).
+ Where("organization_id = ? AND status != ?", organizationId, domain.ClusterStatus_DELETED))
+
+ db.Count(&pg.TotalRows)
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&clusters)
if res.Error != nil {
return nil, res.Error
}
-
- if res.RowsAffected == 0 {
- return out, nil
- }
-
for _, cluster := range clusters {
outCluster := reflectCluster(cluster)
out = append(out, outCluster)
}
-
return
}
-func (r *ClusterRepository) FetchByCloudAccountId(cloudAccountId uuid.UUID) (out []domain.Cluster, err error) {
+func (r *ClusterRepository) FetchByCloudAccountId(cloudAccountId uuid.UUID, pg *pagination.Pagination) (out []domain.Cluster, err error) {
var clusters []Cluster
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+ pg.SortColumn = "updated_at"
+ pg.SortOrder = "DESC"
+ filterFunc := CombinedGormFilter("clusters", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&Cluster{}).Preload("CloudAccount").
+ Where("cloud_account_id = ?", cloudAccountId))
- res := r.db.Preload("CloudAccount").Order("updated_at desc, created_at desc").Find(&clusters, "cloud_account_id = ?", cloudAccountId)
+ db.Count(&pg.TotalRows)
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&clusters)
if res.Error != nil {
return nil, res.Error
}
-
- if res.RowsAffected == 0 {
- return out, nil
- }
-
for _, cluster := range clusters {
outCluster := reflectCluster(cluster)
out = append(out, outCluster)
}
-
return
}
diff --git a/internal/repository/organization.go b/internal/repository/organization.go
index 4c7fce43..7e1312a0 100644
--- a/internal/repository/organization.go
+++ b/internal/repository/organization.go
@@ -1,18 +1,20 @@
package repository
import (
- "github.com/openinfradev/tks-api/pkg/log"
+ "fmt"
+ "math"
"github.com/google/uuid"
- "gorm.io/gorm"
-
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
+ "github.com/openinfradev/tks-api/pkg/log"
+ "gorm.io/gorm"
)
// Interfaces
type IOrganizationRepository interface {
Create(organizationId string, name string, creator uuid.UUID, phone string, description string) (domain.Organization, error)
- Fetch() (res *[]domain.Organization, err error)
+ Fetch(pg *pagination.Pagination) (res *[]domain.Organization, err error)
Get(organizationId string) (res domain.Organization, err error)
Update(organizationId string, in domain.UpdateOrganizationRequest) (domain.Organization, error)
UpdatePrimaryClusterId(organizationId string, primaryClusterId string) error
@@ -69,13 +71,21 @@ func (r *OrganizationRepository) Create(organizationId string, name string, crea
return r.reflect(organization), nil
}
-func (r *OrganizationRepository) Fetch() (*[]domain.Organization, error) {
+func (r *OrganizationRepository) Fetch(pg *pagination.Pagination) (*[]domain.Organization, error) {
var organizations []Organization
var out []domain.Organization
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
- res := r.db.Find(&organizations)
+ filterFunc := CombinedGormFilter("organizations", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&Organization{}))
+ db.Count(&pg.TotalRows)
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&organizations)
if res.Error != nil {
- log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error)
return nil, res.Error
}
for _, organization := range organizations {
diff --git a/internal/repository/repository.go b/internal/repository/repository.go
index 01e45574..33142cd3 100644
--- a/internal/repository/repository.go
+++ b/internal/repository/repository.go
@@ -1,6 +1,11 @@
package repository
-import "gorm.io/gorm"
+import (
+ "fmt"
+ "gorm.io/gorm"
+
+ "github.com/openinfradev/tks-api/internal/pagination"
+)
type FilterFunc func(user *gorm.DB) *gorm.DB
@@ -15,3 +20,36 @@ type Repository struct {
StackTemplate IStackTemplateRepository
Alert IAlertRepository
}
+
+func CombinedGormFilter(table string, filters []pagination.Filter, combinedFilter pagination.CombinedFilter) FilterFunc {
+ return func(db *gorm.DB) *gorm.DB {
+ // and query
+ for _, filter := range filters {
+ if len(filter.Values) > 1 {
+ inQuery := fmt.Sprintf("LOWER(%s.%s::text) in (", table, filter.Column)
+ for _, val := range filter.Values {
+ inQuery = inQuery + fmt.Sprintf("LOWER('%s'),", val)
+ }
+ inQuery = inQuery[:len(inQuery)-1] + ")"
+ db = db.Where(inQuery)
+ } else {
+ if len(filter.Values[0]) > 0 {
+ db = db.Where(fmt.Sprintf("LOWER(%s.%s::text) like LOWER('%%%s%%')", table, filter.Column, filter.Values[0]))
+ }
+ }
+ }
+
+ // or query
+ // id = '123' or description = '345'
+ if len(combinedFilter.Columns) > 0 {
+ orQuery := ""
+ for _, column := range combinedFilter.Columns {
+ orQuery = orQuery + fmt.Sprintf("LOWER(%s.%s::text) like LOWER('%%%s%%') OR ", table, column, combinedFilter.Value)
+ }
+ orQuery = orQuery[:len(orQuery)-3]
+ db = db.Where(orQuery)
+ }
+
+ return db
+ }
+}
diff --git a/internal/repository/stack-template.go b/internal/repository/stack-template.go
index e82b40ef..dc27b967 100644
--- a/internal/repository/stack-template.go
+++ b/internal/repository/stack-template.go
@@ -1,18 +1,22 @@
package repository
import (
+ "fmt"
+ "math"
+
"github.com/google/uuid"
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
)
// Interfaces
type IStackTemplateRepository interface {
Get(stackTemplateId uuid.UUID) (domain.StackTemplate, error)
- Fetch() ([]domain.StackTemplate, error)
+ Fetch(pg *pagination.Pagination) ([]domain.StackTemplate, error)
Create(dto domain.StackTemplate) (stackTemplateId uuid.UUID, err error)
Update(dto domain.StackTemplate) (err error)
Delete(dto domain.StackTemplate) (err error)
@@ -35,8 +39,8 @@ type StackTemplate struct {
ID uuid.UUID `gorm:"primarykey"`
OrganizationId string
Organization Organization `gorm:"foreignKey:OrganizationId"`
- Name string
- Description string
+ Name string `gorm:"index"`
+ Description string `gorm:"index"`
Template string
Version string
CloudService string
@@ -67,9 +71,19 @@ func (r *StackTemplateRepository) Get(stackTemplateId uuid.UUID) (out domain.Sta
}
// [TODO] organizationId 별로 생성하지 않고, 하나의 stackTemplate 을 모든 organization 에서 재사용한다. ( 5월 한정, 추후 rearchitecture 필요)
-func (r *StackTemplateRepository) Fetch() (out []domain.StackTemplate, err error) {
+func (r *StackTemplateRepository) Fetch(pg *pagination.Pagination) (out []domain.StackTemplate, err error) {
var stackTemplates []StackTemplate
- res := r.db.Preload(clause.Associations).Find(&stackTemplates)
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+
+ filterFunc := CombinedGormFilter("stack_templates", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&StackTemplate{}))
+ db.Count(&pg.TotalRows)
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&stackTemplates)
if res.Error != nil {
return nil, res.Error
}
diff --git a/internal/repository/user.go b/internal/repository/user.go
index 2c3f91f7..2571b1f6 100644
--- a/internal/repository/user.go
+++ b/internal/repository/user.go
@@ -1,14 +1,16 @@
package repository
import (
+ "fmt"
+ "math"
"time"
"github.com/google/uuid"
- "github.com/openinfradev/tks-api/pkg/httpErrors"
- "gorm.io/gorm"
-
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/pkg/domain"
+ "github.com/openinfradev/tks-api/pkg/httpErrors"
"github.com/openinfradev/tks-api/pkg/log"
+ "gorm.io/gorm"
)
// Interface
@@ -16,7 +18,8 @@ type IUserRepository interface {
Create(accountId string, organizationId string, password string, name string) (domain.User, error)
CreateWithUuid(uuid uuid.UUID, accountId string, name string, password string, email string,
department string, description string, organizationId string, roleId uuid.UUID) (domain.User, error)
- List(...FilterFunc) (out *[]domain.User, err error)
+ List(filters ...FilterFunc) (out *[]domain.User, err error)
+ ListWithPagination(pg *pagination.Pagination, organizationId string) (out *[]domain.User, err error)
Get(accountId string, organizationId string) (domain.User, error)
GetByUuid(userId uuid.UUID) (domain.User, error)
UpdateWithUuid(uuid uuid.UUID, accountId string, name string, password string, roleId uuid.UUID, email string,
@@ -140,6 +143,7 @@ func (r *UserRepository) NameFilter(name string) FilterFunc {
return user.Where("name = ?", name)
}
}
+
func (r *UserRepository) List(filters ...FilterFunc) (*[]domain.User, error) {
var users []User
var res *gorm.DB
@@ -172,6 +176,34 @@ func (r *UserRepository) List(filters ...FilterFunc) (*[]domain.User, error) {
return &out, nil
}
+
+func (r *UserRepository) ListWithPagination(pg *pagination.Pagination, organizationId string) (*[]domain.User, error) {
+ var users []User
+
+ if pg == nil {
+ pg = pagination.NewDefaultPagination()
+ }
+
+ filterFunc := CombinedGormFilter("users", pg.GetFilters(), pg.CombinedFilter)
+ db := filterFunc(r.db.Model(&User{}).Where("organization_id = ?", organizationId))
+ db.Count(&pg.TotalRows)
+
+ pg.TotalPages = int(math.Ceil(float64(pg.TotalRows) / float64(pg.Limit)))
+ orderQuery := fmt.Sprintf("%s %s", pg.SortColumn, pg.SortOrder)
+ res := db.Preload("Organization").Preload("Role").Offset(pg.GetOffset()).Limit(pg.GetLimit()).Order(orderQuery).Find(&users)
+ if res.Error != nil {
+ log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error)
+ return nil, res.Error
+ }
+
+ var out []domain.User
+ for _, user := range users {
+ out = append(out, r.reflect(user))
+ }
+
+ return &out, nil
+}
+
func (r *UserRepository) Get(accountId string, organizationId string) (domain.User, error) {
user, err := r.getUserByAccountId(accountId, organizationId)
if err != nil {
diff --git a/internal/route/route.go b/internal/route/route.go
index ebf23d8f..429e8c1c 100644
--- a/internal/route/route.go
+++ b/internal/route/route.go
@@ -126,6 +126,7 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa
appServeAppHandler := delivery.NewAppServeAppHandler(usecase.NewAppServeAppUsecase(repoFactory, argoClient))
r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/app-serve-apps", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.CreateAppServeApp))).Methods(http.MethodPost)
r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/app-serve-apps", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.GetAppServeApps))).Methods(http.MethodGet)
+ r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/app-serve-apps/count", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.GetNumOfAppsOnStack))).Methods(http.MethodGet)
r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/app-serve-apps/{appId}", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.GetAppServeApp))).Methods(http.MethodGet)
// TODO: To be implemented
// r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/app-serve-apps/{appId}/tasks/{taskId}", authMiddleware.Handle(http.HandlerFunc(appServeAppHandler.GetAppServeAppTask))).Methods(http.MethodGet)
diff --git a/internal/usecase/alert.go b/internal/usecase/alert.go
index 6e8b8882..99fafa9c 100644
--- a/internal/usecase/alert.go
+++ b/internal/usecase/alert.go
@@ -10,6 +10,7 @@ import (
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
"github.com/google/uuid"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -21,7 +22,7 @@ import (
type IAlertUsecase interface {
Get(ctx context.Context, alertId uuid.UUID) (domain.Alert, error)
GetByName(ctx context.Context, organizationId string, name string) (domain.Alert, error)
- Fetch(ctx context.Context, organizationId string) ([]domain.Alert, error)
+ Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.Alert, error)
Create(ctx context.Context, dto domain.CreateAlertRequest) (err error)
Update(ctx context.Context, dto domain.Alert) error
Delete(ctx context.Context, dto domain.Alert) error
@@ -50,7 +51,7 @@ func (u *AlertUsecase) Create(ctx context.Context, input domain.CreateAlertReque
return fmt.Errorf("No data found")
}
- allClusters, err := u.clusterRepo.Fetch()
+ allClusters, err := u.clusterRepo.Fetch(nil)
if err != nil {
return fmt.Errorf("No clusters")
}
@@ -149,8 +150,8 @@ func (u *AlertUsecase) GetByName(ctx context.Context, organizationId string, nam
return
}
-func (u *AlertUsecase) Fetch(ctx context.Context, organizationId string) (alerts []domain.Alert, err error) {
- alerts, err = u.repo.Fetch(organizationId)
+func (u *AlertUsecase) Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) (alerts []domain.Alert, err error) {
+ alerts, err = u.repo.Fetch(organizationId, pg)
if err != nil {
return nil, err
}
@@ -224,7 +225,7 @@ func (u *AlertUsecase) getOrganizationFromCluster(clusters *[]domain.Cluster, st
func (u *AlertUsecase) makeAdditionalInfo(alert *domain.Alert) {
alert.FiredAt = &alert.CreatedAt
- alert.Status = domain.AlertActionStatus_CREATED
+ //alert.Status = domain.AlertActionStatus_CREATED
if len(alert.AlertActions) > 0 {
alert.TakedAt = &alert.AlertActions[0].CreatedAt
@@ -237,13 +238,13 @@ func (u *AlertUsecase) makeAdditionalInfo(alert *domain.Alert) {
alert.LastTaker = alert.AlertActions[len(alert.AlertActions)-1].Taker
alert.TakedSec = int((alert.AlertActions[0].CreatedAt).Sub(alert.CreatedAt).Seconds())
- alert.Status = alert.AlertActions[len(alert.AlertActions)-1].Status
+ //alert.Status = alert.AlertActions[len(alert.AlertActions)-1].Status
}
}
func (u *AlertUsecase) makeGrafanaUrl(ctx context.Context, primaryCluster domain.Cluster, alert domain.CreateAlertRequestAlert, clusterId domain.ClusterId) (url string) {
primaryGrafanaEndpoint := ""
- appGroups, err := u.appGroupRepo.Fetch(primaryCluster.ID)
+ appGroups, err := u.appGroupRepo.Fetch(primaryCluster.ID, nil)
if err == nil {
for _, appGroup := range appGroups {
if appGroup.AppGroupType == domain.AppGroupType_LMA {
diff --git a/internal/usecase/app-group.go b/internal/usecase/app-group.go
index 1aa528ef..e17fa685 100644
--- a/internal/usecase/app-group.go
+++ b/internal/usecase/app-group.go
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
-
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
argowf "github.com/openinfradev/tks-api/pkg/argo-client"
"github.com/openinfradev/tks-api/pkg/domain"
@@ -17,30 +17,32 @@ import (
)
type IAppGroupUsecase interface {
- Fetch(ctx context.Context, clusterId domain.ClusterId) ([]domain.AppGroup, error)
+ Fetch(ctx context.Context, clusterId domain.ClusterId, pg *pagination.Pagination) ([]domain.AppGroup, error)
Create(ctx context.Context, dto domain.AppGroup) (id domain.AppGroupId, err error)
Get(ctx context.Context, id domain.AppGroupId) (out domain.AppGroup, err error)
- Delete(ctx context.Context, organizationId string, id domain.AppGroupId) (err error)
+ Delete(ctx context.Context, id domain.AppGroupId) (err error)
GetApplications(ctx context.Context, id domain.AppGroupId, applicationType domain.ApplicationType) (out []domain.Application, err error)
UpdateApplication(ctx context.Context, dto domain.Application) (err error)
}
type AppGroupUsecase struct {
- repo repository.IAppGroupRepository
- clusterRepo repository.IClusterRepository
- argo argowf.ArgoClient
+ repo repository.IAppGroupRepository
+ clusterRepo repository.IClusterRepository
+ cloudAccountRepo repository.ICloudAccountRepository
+ argo argowf.ArgoClient
}
func NewAppGroupUsecase(r repository.Repository, argoClient argowf.ArgoClient) IAppGroupUsecase {
return &AppGroupUsecase{
- repo: r.AppGroup,
- clusterRepo: r.Cluster,
- argo: argoClient,
+ repo: r.AppGroup,
+ clusterRepo: r.Cluster,
+ cloudAccountRepo: r.CloudAccount,
+ argo: argoClient,
}
}
-func (u *AppGroupUsecase) Fetch(ctx context.Context, clusterId domain.ClusterId) (out []domain.AppGroup, err error) {
- out, err = u.repo.Fetch(clusterId)
+func (u *AppGroupUsecase) Fetch(ctx context.Context, clusterId domain.ClusterId, pg *pagination.Pagination) (out []domain.AppGroup, err error) {
+ out, err = u.repo.Fetch(clusterId, pg)
if err != nil {
return nil, err
}
@@ -60,7 +62,7 @@ func (u *AppGroupUsecase) Create(ctx context.Context, dto domain.AppGroup) (id d
return "", httpErrors.NewBadRequestError(err, "AG_NOT_FOUND_CLUSTER", "")
}
- resAppGroups, err := u.repo.Fetch(dto.ClusterId)
+ resAppGroups, err := u.repo.Fetch(dto.ClusterId, nil)
if err != nil {
return "", httpErrors.NewBadRequestError(err, "AG_NOT_FOUND_APPGROUP", "")
}
@@ -75,6 +77,28 @@ func (u *AppGroupUsecase) Create(ctx context.Context, dto domain.AppGroup) (id d
}
}
+ // check cloudAccount
+ cloudAccounts, err := u.cloudAccountRepo.Fetch(cluster.OrganizationId, nil)
+ if err != nil {
+ return "", httpErrors.NewBadRequestError(fmt.Errorf("Failed to get cloudAccounts"), "", "")
+ }
+ tksCloudAccountId := cluster.CloudAccountId.String()
+ isExist := false
+ for _, ca := range cloudAccounts {
+ if ca.ID == cluster.CloudAccountId {
+
+ // FOR TEST. ADD MAGIC KEYWORD
+ if strings.Contains(ca.Name, domain.CLOUD_ACCOUNT_INCLUSTER) {
+ tksCloudAccountId = ""
+ }
+ isExist = true
+ break
+ }
+ }
+ if !isExist {
+ return "", httpErrors.NewBadRequestError(fmt.Errorf("Not found cloudAccountId[%s] in organization[%s]", cluster.CloudAccountId, cluster.OrganizationId), "", "")
+ }
+
if dto.ID == "" {
dto.ID, err = u.repo.Create(dto)
} else {
@@ -92,12 +116,13 @@ func (u *AppGroupUsecase) Create(ctx context.Context, dto domain.AppGroup) (id d
"cluster_id=" + dto.ClusterId.String(),
"github_account=" + viper.GetString("git-account"),
"manifest_repo_url=" + viper.GetString("git-base-url") + "/" + viper.GetString("git-account") + "/" + dto.ClusterId.String() + "-manifests",
- "revision=" + viper.GetString("revision"),
+ "base_repo_branch=" + viper.GetString("revision"),
"app_group_id=" + dto.ID.String(),
"keycloak_url=" + strings.TrimSuffix(viper.GetString("keycloak-address"), "/auth"),
"console_url=" + viper.GetString("console-address"),
"alert_tks=" + viper.GetString("external-address") + "/system-api/1.0/alerts",
"alert_slack=" + viper.GetString("alert-slack"),
+ "cloud_account_id=" + tksCloudAccountId,
}
switch dto.AppGroupType {
@@ -134,13 +159,38 @@ func (u *AppGroupUsecase) Get(ctx context.Context, id domain.AppGroupId) (out do
return appGroup, nil
}
-func (u *AppGroupUsecase) Delete(ctx context.Context, organizationId string, id domain.AppGroupId) (err error) {
+func (u *AppGroupUsecase) Delete(ctx context.Context, id domain.AppGroupId) (err error) {
appGroup, err := u.repo.Get(id)
if err != nil {
return fmt.Errorf("No appGroup for deletiing : %s", id)
}
+ cluster, err := u.clusterRepo.Get(appGroup.ClusterId)
+ if err != nil {
+ return httpErrors.NewBadRequestError(err, "AG_NOT_FOUND_CLUSTER", "")
+ }
+ organizationId := cluster.OrganizationId
- clusterId := appGroup.ClusterId
+ // check cloudAccount
+ cloudAccounts, err := u.cloudAccountRepo.Fetch(cluster.OrganizationId, nil)
+ if err != nil {
+ return httpErrors.NewBadRequestError(fmt.Errorf("Failed to get cloudAccounts"), "", "")
+ }
+ tksCloudAccountId := cluster.CloudAccountId.String()
+ isExist := false
+ for _, ca := range cloudAccounts {
+ if ca.ID == cluster.CloudAccountId {
+
+ // FOR TEST. ADD MAGIC KEYWORD
+ if strings.Contains(ca.Name, domain.CLOUD_ACCOUNT_INCLUSTER) {
+ tksCloudAccountId = ""
+ }
+ isExist = true
+ break
+ }
+ }
+ if !isExist {
+ return httpErrors.NewBadRequestError(fmt.Errorf("Not found cloudAccountId[%s] in organization[%s]", cluster.CloudAccountId, cluster.OrganizationId), "", "")
+ }
// Call argo workflow template
workflowTemplate := ""
@@ -164,9 +214,11 @@ func (u *AppGroupUsecase) Delete(ctx context.Context, organizationId string, id
"organization_id=" + organizationId,
"app_group=" + appGroupName,
"github_account=" + viper.GetString("git-account"),
- "cluster_id=" + clusterId.String(),
+ "cluster_id=" + cluster.ID.String(),
"app_group_id=" + id.String(),
"keycloak_url=" + strings.TrimSuffix(viper.GetString("keycloak-address"), "/auth"),
+ "base_repo_branch=" + viper.GetString("revision"),
+ "cloud_account_id=" + tksCloudAccountId,
}
workflowId, err := u.argo.SumbitWorkflowFromWftpl(workflowTemplate, opts)
diff --git a/internal/usecase/app-serve-app.go b/internal/usecase/app-serve-app.go
index 95907b63..63eaee7b 100644
--- a/internal/usecase/app-serve-app.go
+++ b/internal/usecase/app-serve-app.go
@@ -1,6 +1,7 @@
package usecase
import (
+ "encoding/json"
"fmt"
"strconv"
@@ -10,6 +11,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/viper"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
argowf "github.com/openinfradev/tks-api/pkg/argo-client"
"github.com/openinfradev/tks-api/pkg/domain"
@@ -19,9 +21,10 @@ import (
type IAppServeAppUsecase interface {
CreateAppServeApp(app *domain.AppServeApp) (appId string, taskId string, err error)
- GetAppServeApps(organizationId string, showAll bool) ([]domain.AppServeApp, error)
+ GetAppServeApps(organizationId string, showAll bool, pg *pagination.Pagination) ([]domain.AppServeApp, error)
GetAppServeAppById(appId string) (*domain.AppServeApp, error)
GetAppServeAppLatestTask(appId string) (*domain.AppServeAppTask, error)
+ GetNumOfAppsOnStack(organizationId string, clusterId string) (int64, error)
IsAppServeAppExist(appId string) (bool, error)
IsAppServeAppNameExist(orgId string, appName string) (bool, error)
UpdateAppServeAppStatus(appId string, taskId string, status string, output string) (ret string, err error)
@@ -86,7 +89,30 @@ func (u *AppServeAppUsecase) CreateAppServeApp(app *domain.AppServeApp) (string,
}
}
- // TODO: Validate PV params
+ extEnv := app.AppServeAppTasks[0].ExtraEnv
+ if extEnv != "" {
+ /* Preprocess extraEnv param */
+ log.Debug("extraEnv received: ", extEnv)
+
+ tempMap := map[string]string{}
+ err := json.Unmarshal([]byte(extEnv), &tempMap)
+ if err != nil {
+ log.Error(err)
+ return "", "", errors.Wrap(err, "Failed to process extraEnv param.")
+ }
+ log.Debugf("extraEnv marshalled: %v", tempMap)
+
+ newExtEnv := map[string]string{}
+ for key, val := range tempMap {
+ newkey := "\"" + key + "\""
+ newval := "\"" + val + "\""
+ newExtEnv[newkey] = newval
+ }
+
+ mJson, _ := json.Marshal(newExtEnv)
+ extEnv = string(mJson)
+ log.Debug("After transform, extraEnv: ", extEnv)
+ }
appId, taskId, err := u.repo.CreateAppServeApp(app)
if err != nil {
@@ -96,6 +122,8 @@ func (u *AppServeAppUsecase) CreateAppServeApp(app *domain.AppServeApp) (string,
fmt.Printf("appId = %s, taskId = %s", appId, taskId)
+ // TODO: Validate PV params
+
// Call argo workflow
workflow := "serve-java-app"
@@ -114,7 +142,7 @@ func (u *AppServeAppUsecase) CreateAppServeApp(app *domain.AppServeApp) (string,
"image_url=" + app.AppServeAppTasks[0].ImageUrl,
"port=" + app.AppServeAppTasks[0].Port,
"profile=" + app.AppServeAppTasks[0].Profile,
- "extra_env=" + app.AppServeAppTasks[0].ExtraEnv,
+ "extra_env=" + extEnv,
"app_config=" + app.AppServeAppTasks[0].AppConfig,
"app_secret=" + app.AppServeAppTasks[0].AppSecret,
"resource_spec=" + app.AppServeAppTasks[0].ResourceSpec,
@@ -141,8 +169,8 @@ func (u *AppServeAppUsecase) CreateAppServeApp(app *domain.AppServeApp) (string,
return appId, app.Name, nil
}
-func (u *AppServeAppUsecase) GetAppServeApps(organizationId string, showAll bool) ([]domain.AppServeApp, error) {
- apps, err := u.repo.GetAppServeApps(organizationId, showAll)
+func (u *AppServeAppUsecase) GetAppServeApps(organizationId string, showAll bool, pg *pagination.Pagination) ([]domain.AppServeApp, error) {
+ apps, err := u.repo.GetAppServeApps(organizationId, showAll, pg)
if err != nil {
fmt.Println(apps)
}
@@ -168,6 +196,15 @@ func (u *AppServeAppUsecase) GetAppServeAppLatestTask(appId string) (*domain.App
return task, nil
}
+func (u *AppServeAppUsecase) GetNumOfAppsOnStack(organizationId string, clusterId string) (int64, error) {
+ numApps, err := u.repo.GetNumOfAppsOnStack(organizationId, clusterId)
+ if err != nil {
+ return -1, err
+ }
+
+ return numApps, nil
+}
+
func (u *AppServeAppUsecase) IsAppServeAppExist(appId string) (bool, error) {
count, err := u.repo.IsAppServeAppExist(appId)
if err != nil {
@@ -267,6 +304,15 @@ func (u *AppServeAppUsecase) DeleteAppServeApp(appId string) (res string, err er
return "", errors.Wrap(err, "Failed to create delete task.")
}
+ log.Info("Updating app status to 'DELETING'..")
+
+ err = u.repo.UpdateStatus(appId, taskId, "DELETING", "")
+ if err != nil {
+ log.Debug("appId = ", appId)
+ log.Debug("taskId = ", taskId)
+ return "", fmt.Errorf("failed to update app status on DeleteAppServeApp. Err: %s", err)
+ }
+
workflow := "delete-java-app"
log.Info("Submitting workflow: ", workflow)
@@ -333,6 +379,31 @@ func (u *AppServeAppUsecase) UpdateAppServeApp(app *domain.AppServeApp, appTask
}
}
+ extEnv := appTask.ExtraEnv
+ if extEnv != "" {
+ /* Preprocess extraEnv param */
+ log.Debug("extraEnv received: ", extEnv)
+
+ tempMap := map[string]string{}
+ err = json.Unmarshal([]byte(extEnv), &tempMap)
+ if err != nil {
+ log.Error(err)
+ return "", errors.Wrap(err, "Failed to process extraEnv param.")
+ }
+ log.Debugf("extraEnv marshalled: %v", tempMap)
+
+ newExtEnv := map[string]string{}
+ for key, val := range tempMap {
+ newkey := "\"" + key + "\""
+ newval := "\"" + val + "\""
+ newExtEnv[newkey] = newval
+ }
+
+ mJson, _ := json.Marshal(newExtEnv)
+ extEnv = string(mJson)
+ log.Debug("After transform, extraEnv: ", extEnv)
+ }
+
taskId, err := u.repo.CreateTask(appTask)
if err != nil {
log.Info("taskId = ", taskId)
@@ -369,7 +440,7 @@ func (u *AppServeAppUsecase) UpdateAppServeApp(app *domain.AppServeApp, appTask
"image_url=" + appTask.ImageUrl,
"port=" + appTask.Port,
"profile=" + appTask.Profile,
- "extra_env=" + appTask.ExtraEnv,
+ "extra_env=" + extEnv,
"app_config=" + appTask.AppConfig,
"app_secret=" + appTask.AppSecret,
"resource_spec=" + appTask.ResourceSpec,
diff --git a/internal/usecase/auth.go b/internal/usecase/auth.go
index 81e999a1..02b43103 100644
--- a/internal/usecase/auth.go
+++ b/internal/usecase/auth.go
@@ -265,7 +265,7 @@ func (u *AuthUsecase) SingleSignOut(organizationId string) (string, []*http.Cook
return "", nil, err
}
- appGroupsInPrimaryCluster, err := u.appgroupRepository.Fetch(domain.ClusterId(organization.PrimaryClusterId))
+ appGroupsInPrimaryCluster, err := u.appgroupRepository.Fetch(domain.ClusterId(organization.PrimaryClusterId), nil)
if err != nil {
return "", nil, err
}
@@ -349,7 +349,7 @@ func makingCookie(organizationId, userName, password string) ([]*http.Cookie, er
baseUrl := viper.GetString("keycloak-address") + "/realms/" + organizationId + "/protocol/openid-connect"
var oauth2Config = &oauth2.Config{
ClientID: keycloak.DefaultClientID,
- ClientSecret: keycloak.DefaultClientSecret,
+ ClientSecret: viper.GetString("keycloak-client-secret"),
RedirectURL: viper.GetString("external-address") + internal.API_PREFIX + internal.API_VERSION + "/auth/callback",
Scopes: []string{"openid"},
Endpoint: oauth2.Endpoint{
diff --git a/internal/usecase/cloud-account.go b/internal/usecase/cloud-account.go
index e78916dd..713f953d 100644
--- a/internal/usecase/cloud-account.go
+++ b/internal/usecase/cloud-account.go
@@ -8,6 +8,7 @@ import (
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
"github.com/google/uuid"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
argowf "github.com/openinfradev/tks-api/pkg/argo-client"
"github.com/openinfradev/tks-api/pkg/domain"
@@ -23,7 +24,7 @@ type ICloudAccountUsecase interface {
Get(ctx context.Context, cloudAccountId uuid.UUID) (domain.CloudAccount, error)
GetByName(ctx context.Context, organizationId string, name string) (domain.CloudAccount, error)
GetByAwsAccountId(ctx context.Context, awsAccountId string) (domain.CloudAccount, error)
- Fetch(ctx context.Context, organizationId string) ([]domain.CloudAccount, error)
+ Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.CloudAccount, error)
Create(ctx context.Context, dto domain.CloudAccount) (cloudAccountId uuid.UUID, err error)
Update(ctx context.Context, dto domain.CloudAccount) error
Delete(ctx context.Context, dto domain.CloudAccount) error
@@ -69,7 +70,7 @@ func (u *CloudAccountUsecase) Create(ctx context.Context, dto domain.CloudAccoun
log.InfoWithContext(ctx, "newly created CloudAccount ID:", cloudAccountId)
// FOR TEST. ADD MAGIC KEYWORD
- if strings.Contains(dto.Name, "INCLUSTER") {
+ if strings.Contains(dto.Name, domain.CLOUD_ACCOUNT_INCLUSTER) {
if err := u.repo.InitWorkflow(cloudAccountId, "", domain.CloudAccountStatus_CREATED); err != nil {
return uuid.Nil, errors.Wrap(err, "Failed to initialize status")
}
@@ -151,8 +152,8 @@ func (u *CloudAccountUsecase) GetByAwsAccountId(ctx context.Context, awsAccountI
return
}
-func (u *CloudAccountUsecase) Fetch(ctx context.Context, organizationId string) (cloudAccounts []domain.CloudAccount, err error) {
- cloudAccounts, err = u.repo.Fetch(organizationId)
+func (u *CloudAccountUsecase) Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) (cloudAccounts []domain.CloudAccount, err error) {
+ cloudAccounts, err = u.repo.Fetch(organizationId, pg)
if err != nil {
return nil, err
}
@@ -210,8 +211,9 @@ func (u *CloudAccountUsecase) DeleteForce(ctx context.Context, cloudAccountId uu
return err
}
- if cloudAccount.Status != domain.CloudAccountStatus_ERROR {
- return fmt.Errorf("The status is not ERROR. %s", cloudAccount.Status)
+ if !strings.Contains(cloudAccount.Name, domain.CLOUD_ACCOUNT_INCLUSTER) &&
+ cloudAccount.Status != domain.CloudAccountStatus_CREATE_ERROR {
+ return fmt.Errorf("The status is not CREATE_ERROR. %s", cloudAccount.Status)
}
if u.getClusterCnt(cloudAccountId) > 0 {
@@ -229,7 +231,7 @@ func (u *CloudAccountUsecase) DeleteForce(ctx context.Context, cloudAccountId uu
func (u *CloudAccountUsecase) getClusterCnt(cloudAccountId uuid.UUID) (cnt int) {
cnt = 0
- clusters, err := u.clusterRepo.FetchByCloudAccountId(cloudAccountId)
+ clusters, err := u.clusterRepo.FetchByCloudAccountId(cloudAccountId, nil)
if err != nil {
log.Error("Failed to get clusters by cloudAccountId. err : ", err)
return cnt
diff --git a/internal/usecase/cluster.go b/internal/usecase/cluster.go
index 75119f5a..8048c068 100644
--- a/internal/usecase/cluster.go
+++ b/internal/usecase/cluster.go
@@ -8,6 +8,7 @@ import (
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
"github.com/google/uuid"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
argowf "github.com/openinfradev/tks-api/pkg/argo-client"
"github.com/openinfradev/tks-api/pkg/domain"
@@ -21,8 +22,8 @@ import (
type IClusterUsecase interface {
WithTrx(*gorm.DB) IClusterUsecase
- Fetch(ctx context.Context, organizationId string) ([]domain.Cluster, error)
- FetchByCloudAccountId(ctx context.Context, cloudAccountId uuid.UUID) (out []domain.Cluster, err error)
+ Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.Cluster, error)
+ FetchByCloudAccountId(ctx context.Context, cloudAccountId uuid.UUID, pg *pagination.Pagination) (out []domain.Cluster, err error)
Create(ctx context.Context, dto domain.Cluster) (clusterId domain.ClusterId, err error)
Get(ctx context.Context, clusterId domain.ClusterId) (out domain.Cluster, err error)
GetClusterSiteValues(ctx context.Context, clusterId domain.ClusterId) (out domain.ClusterSiteValuesResponse, err error)
@@ -83,12 +84,12 @@ func (u *ClusterUsecase) WithTrx(trxHandle *gorm.DB) IClusterUsecase {
return u
}
-func (u *ClusterUsecase) Fetch(ctx context.Context, organizationId string) (out []domain.Cluster, err error) {
+func (u *ClusterUsecase) Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) (out []domain.Cluster, err error) {
if organizationId == "" {
// [TODO] 사용자가 속한 organization 리스트
- out, err = u.repo.Fetch()
+ out, err = u.repo.Fetch(pg)
} else {
- out, err = u.repo.FetchByOrganizationId(organizationId)
+ out, err = u.repo.FetchByOrganizationId(organizationId, pg)
}
if err != nil {
@@ -97,12 +98,12 @@ func (u *ClusterUsecase) Fetch(ctx context.Context, organizationId string) (out
return out, nil
}
-func (u *ClusterUsecase) FetchByCloudAccountId(ctx context.Context, cloudAccountId uuid.UUID) (out []domain.Cluster, err error) {
+func (u *ClusterUsecase) FetchByCloudAccountId(ctx context.Context, cloudAccountId uuid.UUID, pg *pagination.Pagination) (out []domain.Cluster, err error) {
if cloudAccountId == uuid.Nil {
return nil, fmt.Errorf("Invalid cloudAccountId")
}
- out, err = u.repo.Fetch()
+ out, err = u.repo.Fetch(pg)
if err != nil {
return nil, err
@@ -122,7 +123,7 @@ func (u *ClusterUsecase) Create(ctx context.Context, dto domain.Cluster) (cluste
}
// check cloudAccount
- cloudAccounts, err := u.cloudAccountRepo.Fetch(dto.OrganizationId)
+ cloudAccounts, err := u.cloudAccountRepo.Fetch(dto.OrganizationId, nil)
if err != nil {
return "", httpErrors.NewBadRequestError(fmt.Errorf("Failed to get cloudAccounts"), "", "")
}
@@ -133,7 +134,7 @@ func (u *ClusterUsecase) Create(ctx context.Context, dto domain.Cluster) (cluste
if ca.ID == dto.CloudAccountId {
// FOR TEST. ADD MAGIC KEYWORD
- if strings.Contains(ca.Name, "INCLUSTER") {
+ if strings.Contains(ca.Name, domain.CLOUD_ACCOUNT_INCLUSTER) {
tksCloudAccountId = "NULL"
}
isExist = true
@@ -184,6 +185,7 @@ func (u *ClusterUsecase) Create(ctx context.Context, dto domain.Cluster) (cluste
"git_account=" + viper.GetString("git-account"),
"creator=" + user.GetUserId().String(),
"cloud_account_id=" + tksCloudAccountId,
+ "base_repo_branch=" + viper.GetString("revision"),
//"manifest_repo_url=" + viper.GetString("git-base-url") + "/" + viper.GetString("git-account") + "/" + clusterId + "-manifests",
},
})
@@ -219,7 +221,7 @@ func (u *ClusterUsecase) Delete(ctx context.Context, clusterId domain.ClusterId)
return fmt.Errorf("The cluster can not be deleted. cluster status : %s", cluster.Status)
}
- resAppGroups, err := u.appGroupRepo.Fetch(clusterId)
+ resAppGroups, err := u.appGroupRepo.Fetch(clusterId, nil)
if err != nil {
return errors.Wrap(err, "Failed to get appgroup")
}
@@ -237,7 +239,7 @@ func (u *ClusterUsecase) Delete(ctx context.Context, clusterId domain.ClusterId)
return httpErrors.NewInternalServerError(fmt.Errorf("Failed to get cloudAccount"), "", "")
}
tksCloudAccountId := cluster.CloudAccountId.String()
- if strings.Contains(cloudAccount.Name, "INCLUSTER") {
+ if strings.Contains(cloudAccount.Name, domain.CLOUD_ACCOUNT_INCLUSTER) {
tksCloudAccountId = "NULL"
}
diff --git a/internal/usecase/dashboard.go b/internal/usecase/dashboard.go
index 97a23384..5938f0b5 100644
--- a/internal/usecase/dashboard.go
+++ b/internal/usecase/dashboard.go
@@ -70,7 +70,7 @@ func (u *DashboardUsecase) GetCharts(ctx context.Context, organizationId string,
}
func (u *DashboardUsecase) GetStacks(ctx context.Context, organizationId string) (out []domain.DashboardStack, err error) {
- clusters, err := u.clusterRepo.FetchByOrganizationId(organizationId)
+ clusters, err := u.clusterRepo.FetchByOrganizationId(organizationId, nil)
if err != nil {
return out, err
}
@@ -96,7 +96,7 @@ func (u *DashboardUsecase) GetStacks(ctx context.Context, organizationId string)
}
for _, cluster := range clusters {
- appGroups, err := u.appGroupRepo.Fetch(cluster.ID)
+ appGroups, err := u.appGroupRepo.Fetch(cluster.ID, nil)
if err != nil {
return nil, err
}
@@ -146,7 +146,7 @@ func (u *DashboardUsecase) GetResources(ctx context.Context, organizationId stri
}
// Stack
- clusters, err := u.clusterRepo.FetchByOrganizationId(organizationId)
+ clusters, err := u.clusterRepo.FetchByOrganizationId(organizationId, nil)
if err != nil {
return out, err
}
@@ -185,36 +185,36 @@ func (u *DashboardUsecase) GetResources(ctx context.Context, organizationId stri
if err != nil {
return out, err
}
- memory := 0
+ memory := float64(0)
for _, val := range result.Data.Result {
memoryVal, err := strconv.Atoi(val.Value[1].(string))
if err != nil {
continue
}
if memoryVal > 0 {
- memoryVal = memoryVal / 1024 / 1024 / 1024
- memory = memory + memoryVal
+ memory_ := float64(memoryVal) / float64(1024) / float64(1024) / float64(1024)
+ memory = memory + memory_
}
}
- out.Memory = fmt.Sprintf("%d GB", memory)
+ out.Memory = fmt.Sprintf("%v GiB", math.Round(memory))
// Storage
result, err = thanosClient.Get("sum by (taco_cluster) (kubelet_volume_stats_capacity_bytes)")
if err != nil {
return out, err
}
- storage := 0
+ storage := float64(0)
for _, val := range result.Data.Result {
storageVal, err := strconv.Atoi(val.Value[1].(string))
if err != nil {
continue
}
if storageVal > 0 {
- storageVal = storageVal / 1024 / 1024 / 1024
- storage = storage + storageVal
+ storage_ := float64(storageVal) / float64(1024) / float64(1024) / float64(1024)
+ storage = storage + storage_
}
}
- out.Storage = fmt.Sprintf("%d GB", storage)
+ out.Storage = fmt.Sprintf("%v GiB", math.Round(storage))
return
}
@@ -267,112 +267,17 @@ func (u *DashboardUsecase) getChartFromPrometheus(organizationId string, chartTy
query = "avg by (taco_cluster) (sum(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) by (taco_cluster) / sum(node_memory_MemTotal_bytes) by (taco_cluster))"
case domain.ChartType_POD.String():
- query = "avg by (taco_cluster) (increase(kube_pod_container_status_restarts_total{namespace!=\"kube-system\"}[1h]))"
+ query = "sum by (taco_cluster) (changes(kube_pod_container_status_restarts_total{namespace!=\"kube-system\"}[1h]))"
case domain.ChartType_TRAFFIC.String():
query = "avg by (taco_cluster) (rate(container_network_receive_bytes_total[1h]))"
case domain.ChartType_POD_CALENDAR.String():
- /*
- // 입력받은 년,월 을 date 형식으로
- yearInt, _ := strconv.Atoi(year)
- monthInt, _ := strconv.Atoi(month)
- startDate := time.Date(yearInt, time.Month(monthInt), 1, 0, 0, 0, 0, time.UTC)
- endDate := startDate.Add(time.Hour * 24 * 30)
-
- start := 0
- end := 0
- if now.Year() < yearInt {
- return res, fmt.Errorf("Invalid year")
- } else if now.Year() == yearInt && int(now.Month()) < monthInt {
- return res, fmt.Errorf("Invalid month")
- } else if now.Year() == yearInt && int(now.Month()) == monthInt {
- start = int(startDate.Unix())
- end = int(now.Unix())
- } else {
- start = int(startDate.Unix())
- end = int(endDate.Unix())
- }
-
- log.Debugf("S : %d E : %d", start, end)
-
- query = "sum by (__name__) ({__name__=~\"kube_pod_container_status_restarts_total|kube_pod_status_phase\"})"
-
- result, err := thanosClient.FetchRange(query, start, end, 60*60*24)
- if err != nil {
- return res, err
- }
-
- for _, val := range result.Data.Result {
- xAxisData := []string{}
- yAxisData := []string{}
-
- for _, vals := range val.Values {
- x := int(math.Round(vals.([]interface{})[0].(float64)))
- y, err := strconv.ParseFloat(vals.([]interface{})[1].(string), 32)
- if err != nil {
- y = 0
- }
- xAxisData = append(xAxisData, strconv.Itoa(x))
- yAxisData = append(yAxisData, fmt.Sprintf("%d", int(y)))
- }
-
- if val.Metric.Name == "kube_pod_container_status_restarts_total" {
- chartData.XAxis.Data = xAxisData
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "date",
- Data: xAxisData,
- })
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "podRestartCount",
- Data: yAxisData,
- })
- }
-
- if val.Metric.Name == "kube_pod_status_phase" {
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "totalPodCount",
- Data: yAxisData,
- })
- }
- }
-
-
- {
- series : [
- {
- name : date,
- data : [
- "timestamp1",
- "timestamp2"
- "timestamp3"
- ]
- },
- {
- name : podRestartCount,
- data : [
- "1",
- "2"
- "3"
- ]
- },
- {
- name : totalPodCount,
- data : [
- "10",
- "20"
- "30"
- ]
- },
- ]
- }
- */
-
// 입력받은 년,월 을 date 형식으로
yearInt, _ := strconv.Atoi(year)
monthInt, _ := strconv.Atoi(month)
startDate := time.Date(yearInt, time.Month(monthInt), 1, 0, 0, 0, 0, time.UTC)
- endDate := startDate.Add(time.Hour * 24 * 30)
+ endDate := time.Date(yearInt, time.Month(monthInt+1), 1, 0, 0, 0, 0, time.UTC)
if now.Year() < yearInt {
return res, fmt.Errorf("Invalid year")
@@ -385,20 +290,24 @@ func (u *DashboardUsecase) getChartFromPrometheus(organizationId string, chartTy
return res, err
}
- xAxisData := []string{}
- yAxisData := []string{}
+ organization, err := u.organizationRepo.Get(organizationId)
+ if err != nil {
+ return res, err
+ }
+
+ log.Info(organization.CreatedAt.Format("2006-01-02"))
+ podCounts := []domain.PodCount{}
for day := rangeDate(startDate, endDate); ; {
d := day()
if d.IsZero() {
break
}
- baseDate := d.Format("2006-01-02")
- cntPodRestartStr := ""
+ baseDate := d.Format("2006-01-02")
cntPodRestart := 0
- if baseDate <= now.Format("2006-01-02") {
+ if baseDate <= now.Format("2006-01-02") && baseDate >= organization.CreatedAt.Format("2006-01-02") {
for _, alert := range alerts {
strDate := alert.CreatedAt.Format("2006-01-02")
@@ -406,55 +315,16 @@ func (u *DashboardUsecase) getChartFromPrometheus(organizationId string, chartTy
cntPodRestart += 1
}
}
- cntPodRestartStr = fmt.Sprintf("%d", int(cntPodRestart))
- }
-
- dd := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, time.UTC)
- xAxisData = append(xAxisData, strconv.Itoa(int(dd.Unix())))
- yAxisData = append(yAxisData, cntPodRestartStr)
- }
-
- chartData.XAxis.Data = xAxisData
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "podRestartCount",
- Data: yAxisData,
- })
-
- /*
- for _, alert := range alerts {
- xAxisData := []string{}
- yAxisData := []string{}
-
- for _, vals := range val.Values {
- x := int(math.Round(vals.([]interface{})[0].(float64)))
- y, err := strconv.ParseFloat(vals.([]interface{})[1].(string), 32)
- if err != nil {
- y = 0
- }
- xAxisData = append(xAxisData, strconv.Itoa(x))
- yAxisData = append(yAxisData, fmt.Sprintf("%d", int(y)))
- }
-
- if val.Metric.Name == "kube_pod_container_status_restarts_total" {
- chartData.XAxis.Data = xAxisData
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "date",
- Data: xAxisData,
- })
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "podRestartCount",
- Data: yAxisData,
- })
- }
-
- if val.Metric.Name == "kube_pod_status_phase" {
- chartData.Series = append(chartData.Series, domain.Unit{
- Name: "totalPodCount",
- Data: yAxisData,
- })
+ pd := domain.PodCount{
+ Day: d.Day(),
+ Value: int(cntPodRestart),
}
+ podCounts = append(podCounts, pd)
}
- */
+ }
+ chartData.XAxis = nil
+ chartData.YAxis = nil
+ chartData.PodCounts = podCounts
return domain.DashboardChart{
ChartType: domain.ChartType_POD_CALENDAR,
@@ -470,7 +340,7 @@ func (u *DashboardUsecase) getChartFromPrometheus(organizationId string, chartTy
return domain.DashboardChart{}, fmt.Errorf("No data")
}
- result, err := thanosClient.FetchRange(query, int(now.Unix())-durationSec, int(now.Unix()), intervalSec)
+ result, err := thanosClient.FetchRange(query, int(now.Unix())-durationSec, int(now.Unix())+10000, intervalSec)
if err != nil {
return res, err
}
@@ -508,7 +378,9 @@ func (u *DashboardUsecase) getChartFromPrometheus(organizationId string, chartTy
Name: clusterName,
Data: yAxisData,
})
+
}
+ chartData.XAxis = &domain.Axis{}
chartData.XAxis.Data = xAxisData
return domain.DashboardChart{
diff --git a/internal/usecase/organization.go b/internal/usecase/organization.go
index d84197c8..3e4cc97d 100644
--- a/internal/usecase/organization.go
+++ b/internal/usecase/organization.go
@@ -5,22 +5,22 @@ import (
"fmt"
"strings"
+ "github.com/google/uuid"
"github.com/openinfradev/tks-api/internal/helper"
"github.com/openinfradev/tks-api/internal/keycloak"
- "github.com/openinfradev/tks-api/pkg/httpErrors"
- "github.com/pkg/errors"
- "github.com/spf13/viper"
-
- "github.com/google/uuid"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
argowf "github.com/openinfradev/tks-api/pkg/argo-client"
"github.com/openinfradev/tks-api/pkg/domain"
+ "github.com/openinfradev/tks-api/pkg/httpErrors"
"github.com/openinfradev/tks-api/pkg/log"
+ "github.com/pkg/errors"
+ "github.com/spf13/viper"
)
type IOrganizationUsecase interface {
Create(context.Context, *domain.Organization) (organizationId string, err error)
- Fetch() (*[]domain.Organization, error)
+ Fetch(pg *pagination.Pagination) (*[]domain.Organization, error)
Get(organizationId string) (domain.Organization, error)
Update(organizationId string, in domain.UpdateOrganizationRequest) (domain.Organization, error)
UpdatePrimaryClusterId(organizationId string, clusterId string) (err error)
@@ -64,6 +64,7 @@ func (u *OrganizationUsecase) Create(ctx context.Context, in *domain.Organizatio
argowf.SubmitOptions{
Parameters: []string{
"contract_id=" + organizationId,
+ "base_repo_branch=" + viper.GetString("revision"),
"keycloak_url=" + strings.TrimSuffix(viper.GetString("keycloak-address"), "/auth"),
},
})
@@ -79,8 +80,8 @@ func (u *OrganizationUsecase) Create(ctx context.Context, in *domain.Organizatio
return organizationId, nil
}
-func (u *OrganizationUsecase) Fetch() (out *[]domain.Organization, err error) {
- organizations, err := u.repo.Fetch()
+func (u *OrganizationUsecase) Fetch(pg *pagination.Pagination) (out *[]domain.Organization, err error) {
+ organizations, err := u.repo.Fetch(pg)
if err != nil {
return nil, err
}
diff --git a/internal/usecase/stack-template.go b/internal/usecase/stack-template.go
index c0078f94..4e2731ac 100644
--- a/internal/usecase/stack-template.go
+++ b/internal/usecase/stack-template.go
@@ -4,13 +4,14 @@ import (
"context"
"github.com/google/uuid"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
"github.com/openinfradev/tks-api/pkg/domain"
)
type IStackTemplateUsecase interface {
Get(ctx context.Context, stackTemplate uuid.UUID) (domain.StackTemplate, error)
- Fetch(ctx context.Context) ([]domain.StackTemplate, error)
+ Fetch(ctx context.Context, pg *pagination.Pagination) ([]domain.StackTemplate, error)
Create(ctx context.Context, dto domain.StackTemplate) (stackTemplate uuid.UUID, err error)
Update(ctx context.Context, dto domain.StackTemplate) error
Delete(ctx context.Context, dto domain.StackTemplate) error
@@ -42,8 +43,8 @@ func (u *StackTemplateUsecase) Get(ctx context.Context, stackTemplate uuid.UUID)
return
}
-func (u *StackTemplateUsecase) Fetch(ctx context.Context) (res []domain.StackTemplate, err error) {
- res, err = u.repo.Fetch()
+func (u *StackTemplateUsecase) Fetch(ctx context.Context, pg *pagination.Pagination) (res []domain.StackTemplate, err error) {
+ res, err = u.repo.Fetch(pg)
if err != nil {
return nil, err
}
diff --git a/internal/usecase/stack.go b/internal/usecase/stack.go
index 4181e4c4..f3b69c2b 100644
--- a/internal/usecase/stack.go
+++ b/internal/usecase/stack.go
@@ -6,10 +6,10 @@ import (
"strings"
"time"
- "github.com/openinfradev/tks-api/internal/middleware/auth/request"
-
"github.com/openinfradev/tks-api/internal/helper"
"github.com/openinfradev/tks-api/internal/kubernetes"
+ "github.com/openinfradev/tks-api/internal/middleware/auth/request"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
argowf "github.com/openinfradev/tks-api/pkg/argo-client"
"github.com/openinfradev/tks-api/pkg/domain"
@@ -23,7 +23,7 @@ import (
type IStackUsecase interface {
Get(ctx context.Context, stackId domain.StackId) (domain.Stack, error)
GetByName(ctx context.Context, organizationId string, name string) (domain.Stack, error)
- Fetch(ctx context.Context, organizationId string) ([]domain.Stack, error)
+ Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) ([]domain.Stack, error)
Create(ctx context.Context, dto domain.Stack) (stackId domain.StackId, err error)
Update(ctx context.Context, dto domain.Stack) error
Delete(ctx context.Context, dto domain.Stack) error
@@ -37,6 +37,7 @@ type StackUsecase struct {
cloudAccountRepo repository.ICloudAccountRepository
organizationRepo repository.IOrganizationRepository
stackTemplateRepo repository.IStackTemplateRepository
+ appServeAppRepo repository.IAppServeAppRepository
argo argowf.ArgoClient
}
@@ -47,6 +48,7 @@ func NewStackUsecase(r repository.Repository, argoClient argowf.ArgoClient) ISta
cloudAccountRepo: r.CloudAccount,
organizationRepo: r.Organization,
stackTemplateRepo: r.StackTemplate,
+ appServeAppRepo: r.AppServeApp,
argo: argoClient,
}
}
@@ -72,7 +74,7 @@ func (u *StackUsecase) Create(ctx context.Context, dto domain.Stack) (stackId do
return "", httpErrors.NewInternalServerError(errors.Wrap(err, "Invalid cloudAccountId"), "S_INVALID_CLOUD_ACCOUNT", "")
}
- clusters, err := u.clusterRepo.FetchByOrganizationId(dto.OrganizationId)
+ clusters, err := u.clusterRepo.FetchByOrganizationId(dto.OrganizationId, nil)
if err != nil {
return "", httpErrors.NewInternalServerError(errors.Wrap(err, "Failed to get clusters"), "S_FAILED_GET_CLUSTERS", "")
}
@@ -106,6 +108,7 @@ func (u *StackUsecase) Create(ctx context.Context, dto domain.Stack) (stackId do
"cloud_account_id=" + dto.CloudAccountId.String(),
"stack_template_id=" + dto.StackTemplateId.String(),
"creator=" + user.GetUserId().String(),
+ "base_repo_branch=" + viper.GetString("revision"),
"infra_conf=" + strings.Replace(helper.ModelToJson(stackConf), "\"", "\\\"", -1),
},
})
@@ -156,7 +159,7 @@ func (u *StackUsecase) Get(ctx context.Context, stackId domain.StackId) (out dom
return out, httpErrors.NewInternalServerError(errors.Wrap(err, fmt.Sprintf("Failed to get organization for clusterId %s", cluster.OrganizationId)), "S_FAILED_FETCH_ORGANIZATION", "")
}
- appGroups, err := u.appGroupRepo.Fetch(domain.ClusterId(stackId))
+ appGroups, err := u.appGroupRepo.Fetch(domain.ClusterId(stackId), nil)
if err != nil {
return out, err
}
@@ -166,7 +169,7 @@ func (u *StackUsecase) Get(ctx context.Context, stackId domain.StackId) (out dom
out.PrimaryCluster = true
}
- appGroupsInPrimaryCluster, err := u.appGroupRepo.Fetch(domain.ClusterId(organization.PrimaryClusterId))
+ appGroupsInPrimaryCluster, err := u.appGroupRepo.Fetch(domain.ClusterId(organization.PrimaryClusterId), nil)
if err != nil {
return out, err
}
@@ -195,7 +198,7 @@ func (u *StackUsecase) GetByName(ctx context.Context, organizationId string, nam
return out, err
}
- appGroups, err := u.appGroupRepo.Fetch(cluster.ID)
+ appGroups, err := u.appGroupRepo.Fetch(cluster.ID, nil)
if err != nil {
return out, err
}
@@ -204,19 +207,19 @@ func (u *StackUsecase) GetByName(ctx context.Context, organizationId string, nam
return
}
-func (u *StackUsecase) Fetch(ctx context.Context, organizationId string) (out []domain.Stack, err error) {
+func (u *StackUsecase) Fetch(ctx context.Context, organizationId string, pg *pagination.Pagination) (out []domain.Stack, err error) {
organization, err := u.organizationRepo.Get(organizationId)
if err != nil {
return out, httpErrors.NewInternalServerError(errors.Wrap(err, fmt.Sprintf("Failed to get organization for clusterId %s", organizationId)), "S_FAILED_FETCH_ORGANIZATION", "")
}
- clusters, err := u.clusterRepo.FetchByOrganizationId(organizationId)
+ clusters, err := u.clusterRepo.FetchByOrganizationId(organizationId, pg)
if err != nil {
return out, err
}
for _, cluster := range clusters {
- appGroups, err := u.appGroupRepo.Fetch(cluster.ID)
+ appGroups, err := u.appGroupRepo.Fetch(cluster.ID, nil)
if err != nil {
return nil, err
}
@@ -276,7 +279,7 @@ func (u *StackUsecase) Delete(ctx context.Context, dto domain.Stack) (err error)
}
// 지우려고 하는 stack 이 primary cluster 라면, organization 내에 cluster 가 자기 자신만 남아있을 경우이다.
- organizations, err := u.organizationRepo.Fetch()
+ organizations, err := u.organizationRepo.Fetch(nil)
if err != nil {
return errors.Wrap(err, "Failed to get organizations")
}
@@ -284,23 +287,22 @@ func (u *StackUsecase) Delete(ctx context.Context, dto domain.Stack) (err error)
for _, organization := range *organizations {
if organization.PrimaryClusterId == cluster.ID.String() {
- clusters, err := u.clusterRepo.FetchByOrganizationId(organization.ID)
+ clusters, err := u.clusterRepo.FetchByOrganizationId(organization.ID, nil)
if err != nil {
return errors.Wrap(err, "Failed to get organizations")
}
for _, cl := range clusters {
- if cl.ID != cluster.ID &&
- cl.Status != domain.ClusterStatus_DELETED &&
- cl.Status != domain.ClusterStatus_ERROR {
+ if cl.ID != cluster.ID && (cl.Status == domain.ClusterStatus_RUNNING ||
+ cl.Status == domain.ClusterStatus_INSTALLING ||
+ cl.Status == domain.ClusterStatus_DELETING) {
return httpErrors.NewBadRequestError(fmt.Errorf("Failed to delete 'Primary' cluster. The clusters remain in organization"), "S_REMAIN_CLUSTER_FOR_DELETION", "")
}
}
break
}
}
-
- appGroups, err := u.appGroupRepo.Fetch(domain.ClusterId(dto.ID))
+ appGroups, err := u.appGroupRepo.Fetch(domain.ClusterId(dto.ID), nil)
if err != nil {
return errors.Wrap(err, "Failed to get appGroups")
}
@@ -312,6 +314,14 @@ func (u *StackUsecase) Delete(ctx context.Context, dto domain.Stack) (err error)
}
}
+ appsCnt, err := u.appServeAppRepo.GetNumOfAppsOnStack(dto.OrganizationId, dto.ID.String())
+ if err != nil {
+ return errors.Wrap(err, "Failed to get numOfAppsOnStack")
+ }
+ if appsCnt > 0 {
+ return httpErrors.NewBadRequestError(fmt.Errorf("existed appServeApps in %s", dto.OrganizationId), "S_FAILED_DELETE_EXISTED_ASA", "")
+ }
+
workflow := ""
if strings.Contains(cluster.StackTemplate.Template, "aws-reference") || strings.Contains(cluster.StackTemplate.Template, "eks-reference") {
workflow = "tks-stack-delete-aws"
@@ -425,7 +435,7 @@ func (u *StackUsecase) GetStepStatus(ctx context.Context, stackId domain.StackId
})
}
- appGroups, err := u.appGroupRepo.Fetch(domain.ClusterId(stackId))
+ appGroups, err := u.appGroupRepo.Fetch(domain.ClusterId(stackId), nil)
for _, appGroup := range appGroups {
for i, step := range out {
if step.Stage == appGroup.AppGroupType.String() {
@@ -510,8 +520,11 @@ func getStackStatus(cluster domain.Cluster, appGroups []domain.AppGroup) (domain
if appGroup.Status == domain.AppGroupStatus_DELETING {
return domain.StackStatus_APPGROUP_DELETING, appGroup.StatusDesc
}
- if appGroup.Status == domain.AppGroupStatus_ERROR {
- return domain.StackStatus_APPGROUP_ERROR, appGroup.StatusDesc
+ if appGroup.Status == domain.AppGroupStatus_INSTALL_ERROR {
+ return domain.StackStatus_APPGROUP_INSTALL_ERROR, appGroup.StatusDesc
+ }
+ if appGroup.Status == domain.AppGroupStatus_DELETE_ERROR {
+ return domain.StackStatus_APPGROUP_DELETE_ERROR, appGroup.StatusDesc
}
}
@@ -524,8 +537,11 @@ func getStackStatus(cluster domain.Cluster, appGroups []domain.AppGroup) (domain
if cluster.Status == domain.ClusterStatus_DELETED {
return domain.StackStatus_CLUSTER_DELETED, cluster.StatusDesc
}
- if cluster.Status == domain.ClusterStatus_ERROR {
- return domain.StackStatus_CLUSTER_ERROR, cluster.StatusDesc
+ if cluster.Status == domain.ClusterStatus_INSTALL_ERROR {
+ return domain.StackStatus_CLUSTER_INSTALL_ERROR, cluster.StatusDesc
+ }
+ if cluster.Status == domain.ClusterStatus_DELETE_ERROR {
+ return domain.StackStatus_CLUSTER_DELETE_ERROR, cluster.StatusDesc
}
// workflow 중간 중간 비는 status 처리...
diff --git a/internal/usecase/user.go b/internal/usecase/user.go
index 87d2a6f9..a65b6649 100644
--- a/internal/usecase/user.go
+++ b/internal/usecase/user.go
@@ -5,13 +5,13 @@ import (
"fmt"
"net/http"
- "github.com/openinfradev/tks-api/internal/aws/ses"
- "github.com/openinfradev/tks-api/internal/middleware/auth/request"
-
"github.com/Nerzal/gocloak/v13"
"github.com/google/uuid"
+ "github.com/openinfradev/tks-api/internal/aws/ses"
"github.com/openinfradev/tks-api/internal/helper"
"github.com/openinfradev/tks-api/internal/keycloak"
+ "github.com/openinfradev/tks-api/internal/middleware/auth/request"
+ "github.com/openinfradev/tks-api/internal/pagination"
"github.com/openinfradev/tks-api/internal/repository"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/openinfradev/tks-api/pkg/httpErrors"
@@ -25,6 +25,7 @@ type IUserUsecase interface {
DeleteAll(ctx context.Context, organizationId string) error
Create(ctx context.Context, user *domain.User) (*domain.User, error)
List(ctx context.Context, organizationId string) (*[]domain.User, error)
+ ListWithPagination(ctx context.Context, organizationId string, pg *pagination.Pagination) (*[]domain.User, error)
Get(userId uuid.UUID) (*domain.User, error)
Update(ctx context.Context, userId uuid.UUID, user *domain.User) (*domain.User, error)
ResetPassword(userId uuid.UUID) error
@@ -321,13 +322,22 @@ func (u *UserUsecase) UpdatePasswordByAccountId(ctx context.Context, accountId s
return nil
}
-func (u *UserUsecase) List(ctx context.Context, organizationId string) (*[]domain.User, error) {
- users, err := u.userRepository.List(u.userRepository.OrganizationFilter(organizationId))
+func (u *UserUsecase) List(ctx context.Context, organizationId string) (users *[]domain.User, err error) {
+ users, err = u.userRepository.List(u.userRepository.OrganizationFilter(organizationId))
+ if err != nil {
+ return nil, err
+ }
+
+ return
+}
+
+func (u *UserUsecase) ListWithPagination(ctx context.Context, organizationId string, pg *pagination.Pagination) (users *[]domain.User, err error) {
+ users, err = u.userRepository.ListWithPagination(pg, organizationId)
if err != nil {
return nil, err
}
- return users, nil
+ return
}
func (u *UserUsecase) Get(userId uuid.UUID) (*domain.User, error) {
diff --git a/pkg/domain/alert.go b/pkg/domain/alert.go
index fb40c92c..7201ef24 100644
--- a/pkg/domain/alert.go
+++ b/pkg/domain/alert.go
@@ -155,7 +155,8 @@ type AlertActionResponse struct {
}
type GetAlertsResponse struct {
- Alerts []AlertResponse `json:"alerts"`
+ Alerts []AlertResponse `json:"alerts"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetAlertResponse struct {
diff --git a/pkg/domain/app-group.go b/pkg/domain/app-group.go
index 76907997..d4e3211d 100644
--- a/pkg/domain/app-group.go
+++ b/pkg/domain/app-group.go
@@ -26,7 +26,8 @@ const (
AppGroupStatus_RUNNING
AppGroupStatus_DELETING
AppGroupStatus_DELETED
- AppGroupStatus_ERROR
+ AppGroupStatus_INSTALL_ERROR
+ AppGroupStatus_DELETE_ERROR
)
var appGroupStatus = [...]string{
@@ -35,7 +36,8 @@ var appGroupStatus = [...]string{
"RUNNING",
"DELETING",
"DELETED",
- "ERROR",
+ "INSTALL_ERROR",
+ "DELETE_ERROR",
}
func (m AppGroupStatus) String() string { return appGroupStatus[(m)] }
@@ -182,7 +184,8 @@ type CreateApplicationRequest struct {
}
type GetAppGroupsResponse struct {
- AppGroups []AppGroupResponse `json:"appGroups"`
+ AppGroups []AppGroupResponse `json:"appGroups"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetAppGroupResponse struct {
diff --git a/pkg/domain/app-serve-app.go b/pkg/domain/app-serve-app.go
index 525a4ce9..4edcc92d 100644
--- a/pkg/domain/app-serve-app.go
+++ b/pkg/domain/app-serve-app.go
@@ -9,7 +9,7 @@ import (
type AppServeApp struct {
ID string `gorm:"primarykey" json:"id,omitempty"`
- Name string `json:"name,omitempty"` // application name
+ Name string `gorm:"index" json:"name,omitempty"` // application name
Namespace string `json:"namespace,omitempty"` // application namespace
OrganizationId string `json:"organizationId,omitempty"` // contractId is a contract ID which this app belongs to
Type string `json:"type,omitempty"` // type (build/deploy/all)
@@ -18,7 +18,7 @@ type AppServeApp struct {
PreviewEndpointUrl string `json:"previewEndpointUrl,omitempty"` // preview svc endpoint URL in B/G deployment
TargetClusterId string `json:"targetClusterId,omitempty"` // target cluster to which the app is deployed
TargetClusterName string `gorm:"-:all" json:"targetClusterName,omitempty"` // target cluster name
- Status string `json:"status,omitempty"` // status is status of deployed app
+ Status string `gorm:"index" json:"status,omitempty"` // status is status of deployed app
CreatedAt time.Time `gorm:"autoCreateTime:false" json:"createdAt" `
UpdatedAt *time.Time `gorm:"autoUpdateTime:false" json:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt"`
@@ -159,7 +159,8 @@ type RollbackAppServeAppRequest struct {
}
type GetAppServeAppsResponse struct {
- AppServeApps []AppServeApp `json:"appServeApps"`
+ AppServeApps []AppServeApp `json:"appServeApps"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetAppServeAppResponse struct {
diff --git a/pkg/domain/cloud-account.go b/pkg/domain/cloud-account.go
index d40706c5..981f7589 100644
--- a/pkg/domain/cloud-account.go
+++ b/pkg/domain/cloud-account.go
@@ -6,6 +6,8 @@ import (
"github.com/google/uuid"
)
+const CLOUD_ACCOUNT_INCLUSTER = "INCLUSTER"
+
const (
CloudService_UNDEFINED = "UNDEFINED"
CloudService_AWS = "AWS"
@@ -22,7 +24,8 @@ const (
CloudAccountStatus_CREATED
CloudAccountStatus_DELETING
CloudAccountStatus_DELETED
- CloudAccountStatus_ERROR
+ CloudAccountStatus_CREATE_ERROR
+ CloudAccountStatus_DELETE_ERROR
)
var cloudAccountStatus = [...]string{
@@ -31,7 +34,8 @@ var cloudAccountStatus = [...]string{
"CREATED",
"DELETING",
"DELETED",
- "ERROR",
+ "CREATE_ERROR",
+ "DELETE_ERROR",
}
func (m CloudAccountStatus) String() string { return cloudAccountStatus[(m)] }
@@ -41,7 +45,7 @@ func (m CloudAccountStatus) FromString(s string) CloudAccountStatus {
return CloudAccountStatus(i)
}
}
- return CloudAccountStatus_ERROR
+ return CloudAccountStatus_PENDING
}
// 내부
@@ -59,6 +63,7 @@ type CloudAccount struct {
SessionToken string
Status CloudAccountStatus
StatusDesc string
+ CreatedIAM bool
CreatorId uuid.UUID
Creator User
UpdatorId uuid.UUID
@@ -77,6 +82,7 @@ type CloudAccountResponse struct {
Clusters int `json:"clusters"`
Status string `json:"status"`
AwsAccountId string `json:"awsAccountId"`
+ CreatedIAM bool `json:"createdIAM"`
Creator SimpleUserResponse `json:"creator"`
Updator SimpleUserResponse `json:"updator"`
CreatedAt time.Time `json:"createdAt"`
@@ -90,11 +96,13 @@ type SimpleCloudAccountResponse struct {
Description string `json:"description"`
CloudService string `json:"cloudService"`
AwsAccountId string `json:"awsAccountId"`
+ CreatedIAM bool `json:"createdIAM"`
Clusters int `json:"clusters"`
}
type GetCloudAccountsResponse struct {
CloudAccounts []CloudAccountResponse `json:"cloudAccounts"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetCloudAccountResponse struct {
diff --git a/pkg/domain/cluster.go b/pkg/domain/cluster.go
index 28446dc1..ab9264ff 100644
--- a/pkg/domain/cluster.go
+++ b/pkg/domain/cluster.go
@@ -26,7 +26,8 @@ const (
ClusterStatus_RUNNING
ClusterStatus_DELETING
ClusterStatus_DELETED
- ClusterStatus_ERROR
+ ClusterStatus_INSTALL_ERROR
+ ClusterStatus_DELETE_ERROR
)
var clusterStatus = [...]string{
@@ -35,7 +36,8 @@ var clusterStatus = [...]string{
"RUNNING",
"DELETING",
"DELETED",
- "ERROR",
+ "INSTALL_ERROR",
+ "DELETE_ERROR",
}
func (m ClusterStatus) String() string { return clusterStatus[(m)] }
@@ -45,7 +47,7 @@ func (m ClusterStatus) FromString(s string) ClusterStatus {
return ClusterStatus(i)
}
}
- return ClusterStatus_ERROR
+ return ClusterStatus_PENDING
}
// model
@@ -160,7 +162,8 @@ type ClusterSiteValuesResponse struct {
}
type GetClustersResponse struct {
- Clusters []ClusterResponse `json:"clusters"`
+ Clusters []ClusterResponse `json:"clusters"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetClusterResponse struct {
diff --git a/pkg/domain/dashboard.go b/pkg/domain/dashboard.go
index b27616a0..c7699fb7 100644
--- a/pkg/domain/dashboard.go
+++ b/pkg/domain/dashboard.go
@@ -81,10 +81,16 @@ type Axis struct {
Data []string `json:"data"`
}
+type PodCount struct {
+ Day int `json:"day"`
+ Value int `json:"value"`
+}
+
type ChartData struct {
- XAxis Axis `json:"xAxis"`
- YAxis Axis `json:"yAxis"`
- Series []Unit `json:"series"`
+ XAxis *Axis `json:"xAxis,omitempty"`
+ YAxis *Axis `json:"yAxis,omitempty"`
+ Series []Unit `json:"series,omitempty"`
+ PodCounts []PodCount `json:"podCounts,omitempty"`
}
type DashboardChartResponse struct {
diff --git a/pkg/domain/organization.go b/pkg/domain/organization.go
index d7801c88..6e3706da 100644
--- a/pkg/domain/organization.go
+++ b/pkg/domain/organization.go
@@ -88,6 +88,7 @@ type GetOrganizationResponse struct {
}
type ListOrganizationResponse struct {
Organizations []ListOrganizationBody `json:"organizations"`
+ Pagination PaginationResponse `json:"pagination"`
}
type ListOrganizationBody struct {
ID string `json:"id"`
diff --git a/pkg/domain/pagination.go b/pkg/domain/pagination.go
new file mode 100644
index 00000000..33efaef7
--- /dev/null
+++ b/pkg/domain/pagination.go
@@ -0,0 +1,16 @@
+package domain
+
+type PaginationResponse struct {
+ Limit int `json:"pageSize"`
+ Page int `json:"pageNumber"`
+ SortColumn string `json:"sortColumn"`
+ SortOrder string `json:"sortOrder"`
+ Filters []FilterResponse `json:"filters,omitempty"`
+ TotalRows int64 `json:"totalRows"`
+ TotalPages int `json:"totalPages"`
+}
+
+type FilterResponse struct {
+ Column string `json:"column"`
+ Values []string `json:"values"`
+}
diff --git a/pkg/domain/stack-template.go b/pkg/domain/stack-template.go
index 9cdc71e8..0be6ae35 100644
--- a/pkg/domain/stack-template.go
+++ b/pkg/domain/stack-template.go
@@ -65,6 +65,7 @@ type SimpleStackTemplateResponse struct {
type GetStackTemplatesResponse struct {
StackTemplates []StackTemplateResponse `json:"stackTemplates"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetStackTemplateResponse struct {
diff --git a/pkg/domain/stack.go b/pkg/domain/stack.go
index 9e1b742c..8f65f12b 100644
--- a/pkg/domain/stack.go
+++ b/pkg/domain/stack.go
@@ -25,28 +25,30 @@ const (
StackStatus_APPGROUP_INSTALLING
StackStatus_APPGROUP_DELETING
- StackStatus_APPGROUP_ERROR
+ StackStatus_APPGROUP_INSTALL_ERROR
+ StackStatus_APPGROUP_DELETE_ERROR
StackStatus_CLUSTER_INSTALLING
StackStatus_CLUSTER_DELETING
StackStatus_CLUSTER_DELETED
- StackStatus_CLUSTER_ERROR
+ StackStatus_CLUSTER_INSTALL_ERROR
+ StackStatus_CLUSTER_DELETE_ERROR
StackStatus_RUNNING
- StackStatus_ERROR
)
var stackStatus = [...]string{
"PENDING",
"APPGROUP_INSTALLING",
"APPGROUP_DELETING",
- "APPGROUP_ERROR",
+ "APPGROUP_INSTALL_ERROR",
+ "APPGROUP_DELETE_ERROR",
"CLUSTER_INSTALLING",
"CLUSTER_DELETING",
"CLUSTER_DELETED",
- "CLUSTER_ERROR",
+ "CLUSTER_INSTALL_ERROR",
+ "CLUSTER_DELETE_ERROR",
"RUNNING",
- "ERROR",
}
func (m StackStatus) String() string { return stackStatus[(m)] }
@@ -56,12 +58,12 @@ func (m StackStatus) FromString(s string) StackStatus {
return StackStatus(i)
}
}
- return StackStatus_ERROR
+ return StackStatus_PENDING
}
const MAX_STEP_CLUSTER_CREATE = 13
const MAX_STEP_CLUSTER_REMOVE = 11
-const MAX_STEP_LMA_CREATE_PRIMARY = 36
+const MAX_STEP_LMA_CREATE_PRIMARY = 39
const MAX_STEP_LMA_CREATE_MEMBER = 27
const MAX_STEP_LMA_REMOVE = 11
const MAX_STEP_SM_CREATE = 22
@@ -151,7 +153,8 @@ type StackResponse struct {
}
type GetStacksResponse struct {
- Stacks []StackResponse `json:"stacks"`
+ Stacks []StackResponse `json:"stacks"`
+ Pagination PaginationResponse `json:"pagination"`
}
type GetStackResponse struct {
diff --git a/pkg/domain/user.go b/pkg/domain/user.go
index 2fc2a1b4..fbea7635 100644
--- a/pkg/domain/user.go
+++ b/pkg/domain/user.go
@@ -94,7 +94,8 @@ type GetUserResponse struct {
}
type ListUserResponse struct {
- Users []ListUserBody `json:"users"`
+ Users []ListUserBody `json:"users"`
+ Pagination PaginationResponse `json:"pagination"`
}
type ListUserBody struct {
ID string `json:"id"`
diff --git a/pkg/httpErrors/errorCode.go b/pkg/httpErrors/errorCode.go
index e13cf2fa..61785284 100644
--- a/pkg/httpErrors/errorCode.go
+++ b/pkg/httpErrors/errorCode.go
@@ -52,6 +52,7 @@ var errorMap = map[ErrorCode]string{
"S_FAILED_TO_CALL_WORKFLOW": "스택 생성에 실패하였습니다. 관리자에게 문의하세요.",
"S_REMAIN_CLUSTER_FOR_DELETION": "프라이머리 클러스터를 지우기 위해서는 조직내의 모든 클러스터를 삭제해야 합니다.",
"S_FAILED_GET_CLUSTERS": "클러스터를 가져오는데 실패했습니다.",
+ "S_FAILED_DELETE_EXISTED_ASA": "지우고자 하는 스택에 남아 있는 앱서빙앱이 있습니다.",
// Alert
"AL_NOT_FOUND_ALERT": "지정한 앨럿이 존재하지 않습니다.",
diff --git a/scripts/init_postgres.sql b/scripts/init_postgres.sql
index 2d1cb14d..6434a1d7 100644
--- a/scripts/init_postgres.sql
+++ b/scripts/init_postgres.sql
@@ -19,14 +19,14 @@ insert into cloud_accounts ( id, name, description, organization_id, cloud_servi
values ( 'ce9e0387-01cb-4f37-a22a-fb91b6338434', 'aws', 'aws_description', 'master', 'AWS', 'result', now(), now() );
insert into stack_templates ( id, organization_id, name, description, version, cloud_service, platform, template, kube_version, kube_type, created_at, updated_at, services )
-values ( '49901092-be76-4d4f-94e9-b84525f560b5', 'master', 'AWS Standard (x86)', 'included LMA', 'v1', 'AWS', 'x86', 'aws-reference', 'v1.24', 'AWS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}]' );
+values ( '49901092-be76-4d4f-94e9-b84525f560b5', 'master', 'AWS Standard (x86)', 'included LMA', 'v1', 'AWS', 'x86', 'aws-reference', 'v1.25', 'AWS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}]' );
insert into stack_templates ( id, organization_id, name, description, version, cloud_service, platform, template, kube_version, kube_type, created_at, updated_at, services )
-values ( '44d5e76b-63db-4dd0-a16e-11bd3f6054cf', 'master', 'AWS MSA Standard (x86)', 'included LMA, SERVICE MESH', 'v1', 'AWS', 'x86', 'aws-msa-reference', 'v1.24', 'AWS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}, {"name": "MSA", "type": "SERVICE_MESH", "applications": [{"name": "istio", "version": "v1.13.1", "description": "MSA 플랫폼"}, {"name": "jagger", "version": "v2.27.1", "description": "분산 서비스간 트랜잭션 추적을 위한 로깅 플랫폼"}, {"name": "kiali", "version": "v1.45.1", "description": "MSA 통합 모니터링포탈"}]}]' );
+values ( '44d5e76b-63db-4dd0-a16e-11bd3f6054cf', 'master', 'AWS MSA Standard (x86)', 'included LMA, SERVICE MESH', 'v1', 'AWS', 'x86', 'aws-msa-reference', 'v1.25', 'AWS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}, {"name": "MSA", "type": "SERVICE_MESH", "applications": [{"name": "istio", "version": "v1.13.1", "description": "MSA 플랫폼"}, {"name": "jagger", "version": "v2.27.1", "description": "분산 서비스간 트랜잭션 추적을 위한 로깅 플랫폼"}, {"name": "kiali", "version": "v1.45.1", "description": "MSA 통합 모니터링포탈"}]}]' );
insert into stack_templates ( id, organization_id, name, description, version, cloud_service, platform, template, kube_version, kube_type, created_at, updated_at, services )
-values ( 'fe1d97e0-7428-4be6-9c69-310a88b4ff46', 'master', 'AWS Standard (arm)', 'included LMA', 'v2', 'AWS', 'arm', 'aws-arm-reference', 'v1.24', 'EKS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}]' );
+values ( 'fe1d97e0-7428-4be6-9c69-310a88b4ff46', 'master', 'AWS Standard (arm)', 'included LMA', 'v2', 'AWS', 'arm', 'aws-arm-reference', 'v1.25', 'EKS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}]' );
insert into stack_templates ( id, organization_id, name, description, version, cloud_service, platform, template, kube_version, kube_type, created_at, updated_at, services )
-values ( '3696cb38-4da0-4235-97eb-b6eb15962bd1', 'master', 'AWS Standard (arm)', 'included LMA, SERVICE_MESH', 'v2', 'AWS', 'arm', 'aws-arm-msa-reference', 'v1.24', 'EKS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}, {"name": "MSA", "type": "SERVICE_MESH", "applications": [{"name": "istio", "version": "v1.13.1", "description": "MSA 플랫폼"}, {"name": "jagger", "version": "v2.27.1", "description": "분산 서비스간 트랜잭션 추적을 위한 로깅 플랫폼"}, {"name": "kiali", "version": "v1.45.1", "description": "MSA 통합 모니터링포탈"}]}]' );
+values ( '3696cb38-4da0-4235-97eb-b6eb15962bd1', 'master', 'AWS Standard (arm)', 'included LMA, SERVICE_MESH', 'v2', 'AWS', 'arm', 'aws-arm-msa-reference', 'v1.25', 'EKS', now(), now(), '[{"name": "Logging,Monitoring,Alerting", "type": "LMA", "applications": [{"name": "prometheus-stack", "version": "v.44.3.1", "description": "통계데이터 제공을 위한 backend 플랫폼"}, {"name": "elastic-system", "version": "v1.8.0", "description": "로그 데이터 적재를 위한 Storage"}, {"name": "alertmanager", "version": "v0.23.0", "description": "Alert 관리를 위한 backend 서비스"}, {"name": "grafana", "version": "v6.50.7", "description": "모니터링 통합 포탈"}]}, {"name": "MSA", "type": "SERVICE_MESH", "applications": [{"name": "istio", "version": "v1.13.1", "description": "MSA 플랫폼"}, {"name": "jagger", "version": "v2.27.1", "description": "분산 서비스간 트랜잭션 추적을 위한 로깅 플랫폼"}, {"name": "kiali", "version": "v1.45.1", "description": "MSA 통합 모니터링포탈"}]}]' );
insert into stack_templates ( id, organization_id, name, description, version, cloud_service, platform, template, kube_version, kube_type, created_at, updated_at, services )
-values ( 'c8a4658d-d5a6-4191-8a91-e26f6aee007f', 'master', 'EKS Standard (x86)', 'included LMA', 'v1', 'AWS', 'x86', 'eks-reference', 'v1.24', 'AWS', now(), now(), '[{"name":"Logging,Monitoring,Alerting","type":"LMA","applications":[{"name":"thanos","version":"0.17.2","description":"다중클러스터의 모니터링 데이터 통합 질의처리"},{"name":"prometheus-stack","version":"v0.62.0","description":"모니터링 데이터 수집/저장 및 질의처리"},{"name":"alertmanager","version":"v0.23.0","description":"알람 처리를 위한 노티피케이션 서비스"},{"name":"loki","version":"2.6.1","description":"로그데이터 저장 및 질의처리"},{"name":"grafana","version":"9.3.6","description":"모니터링/로그 통합대시보드"}]}]' );
+values ( 'c8a4658d-d5a6-4191-8a91-e26f6aee007f', 'master', 'EKS Standard (x86)', 'included LMA', 'v1', 'AWS', 'x86', 'eks-reference', 'v1.25', 'AWS', now(), now(), '[{"name":"Logging,Monitoring,Alerting","type":"LMA","applications":[{"name":"thanos","version":"0.17.2","description":"다중클러스터의 모니터링 데이터 통합 질의처리"},{"name":"prometheus-stack","version":"v0.62.0","description":"모니터링 데이터 수집/저장 및 질의처리"},{"name":"alertmanager","version":"v0.23.0","description":"알람 처리를 위한 노티피케이션 서비스"},{"name":"loki","version":"2.6.1","description":"로그데이터 저장 및 질의처리"},{"name":"grafana","version":"9.3.6","description":"모니터링/로그 통합대시보드"}]}]' );
insert into stack_templates ( id, organization_id, name, description, version, cloud_service, platform, template, kube_version, kube_type, created_at, updated_at, services )
-values ( '39f18a09-5b94-4772-bdba-e4c32ee002f7', 'master', 'EKS MSA Standard (x86)', 'included LMA, SERVICE MESH', 'v1', 'AWS', 'x86', 'eks-msa-reference', 'v1.24', 'AWS', now(), now(), '[{"name":"Logging,Monitoring,Alerting","type":"LMA","applications":[{"name":"thanos","version":"0.17.2","description":"다중클러스터의 모니터링 데이터 통합 질의처리"},{"name":"prometheus-stack","version":"v0.62.0","description":"모니터링 데이터 수집/저장 및 질의처리"},{"name":"alertmanager","version":"v0.23.0","description":"알람 처리를 위한 노티피케이션 서비스"},{"name":"loki","version":"2.6.1","description":"로그데이터 저장 및 질의처리"},{"name":"grafana","version":"9.3.6","description":"모니터링/로그 통합대시보드"}]},{"name":"MSA","type":"SERVICE_MESH","applications":[{"name":"istio","version":"v1.17.2","description":"MSA 플랫폼"},{"name":"jagger","version":"1.35.0","description":"분산 서비스간 트랜잭션 추적을 위한 플랫폼"},{"name":"kiali","version":"v1.63.0","description":"MSA 구조 및 성능을 볼 수 있는 Dashboard"},{"name":"k8ssandra","version":"1.6.0","description":"분산 서비스간 호출 로그를 저장하는 스토리지"}]}]' );
+values ( '39f18a09-5b94-4772-bdba-e4c32ee002f7', 'master', 'EKS MSA Standard (x86)', 'included LMA, SERVICE MESH', 'v1', 'AWS', 'x86', 'eks-msa-reference', 'v1.25', 'AWS', now(), now(), '[{"name":"Logging,Monitoring,Alerting","type":"LMA","applications":[{"name":"thanos","version":"0.17.2","description":"다중클러스터의 모니터링 데이터 통합 질의처리"},{"name":"prometheus-stack","version":"v0.62.0","description":"모니터링 데이터 수집/저장 및 질의처리"},{"name":"alertmanager","version":"v0.23.0","description":"알람 처리를 위한 노티피케이션 서비스"},{"name":"loki","version":"2.6.1","description":"로그데이터 저장 및 질의처리"},{"name":"grafana","version":"9.3.6","description":"모니터링/로그 통합대시보드"}]},{"name":"MSA","type":"SERVICE_MESH","applications":[{"name":"istio","version":"v1.17.2","description":"MSA 플랫폼"},{"name":"jagger","version":"1.35.0","description":"분산 서비스간 트랜잭션 추적을 위한 플랫폼"},{"name":"kiali","version":"v1.63.0","description":"MSA 구조 및 성능을 볼 수 있는 Dashboard"},{"name":"k8ssandra","version":"1.6.0","description":"분산 서비스간 호출 로그를 저장하는 스토리지"}]}]' );