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 @@ + + + + + 이메일인증 안내 + + + +
+ + + + + + + + + + + + + + + +
SKT Enterprise
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 이메일 인증 안내 +
안녕하세요.
항상 저희 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 @@ + + + + + 조직 생성 + + + +
+ + + + + + + + + + + + + + + +
SKT Enterprise
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ​ + + + + + + + + + + + + +
+ 조직 생성 안내 +
안녕하세요.
항상 저희 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 @@ + + + + + 임시 비밀번호 발급 + + + +
+ + + + + + + + + + + + + + + +
SKT Enterprise
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 임시 비밀번호 발급 안내 +
안녕하세요.
항상 저희 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":"분산 서비스간 호출 로그를 저장하는 스토리지"}]}]' );