From e404c7cc5931ec102527c8fa698c780e21568224 Mon Sep 17 00:00:00 2001 From: Yunkon Kim Date: Tue, 19 Nov 2024 22:10:21 +0900 Subject: [PATCH] Support Object Storage APIs for AWS and Azure * Support APIs to create, read, and delete (CRD) Object Storage * Note - This API requires mc-terrarium. --- docker-compose.yaml | 2 +- go.mod | 2 +- go.sum | 4 + go.work.sum | 3 +- src/api/rest/docs/docs.go | 387 ++++++- src/api/rest/docs/swagger.json | 387 ++++++- src/api/rest/docs/swagger.yaml | 309 ++++- src/api/rest/server/common/label/label.go | 8 +- src/api/rest/server/resource/objectStorage.go | 622 ++++++++++ src/api/rest/server/resource/sqlDb.go | 32 +- src/api/rest/server/server.go | 21 +- src/core/common/utility.go | 1 + src/core/model/common.go | 1 + src/core/model/objectStorage.go | 58 + src/core/model/sqlDb.go | 36 +- src/core/resource/common.go | 1 + src/core/resource/objectStorage.go | 1010 +++++++++++++++++ src/core/resource/sqlDb.go | 50 +- 18 files changed, 2812 insertions(+), 122 deletions(-) create mode 100644 src/api/rest/server/resource/objectStorage.go create mode 100644 src/core/model/objectStorage.go create mode 100644 src/core/resource/objectStorage.go diff --git a/docker-compose.yaml b/docker-compose.yaml index 51000866..93e74d2d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -166,7 +166,7 @@ services: # # mc-terrarium (PoC): resource extentions such as VPN for CB-Tumblebug by using OpenTofu # mc-terrarium: - # image: cloudbaristaorg/mc-terrarium:0.0.16 + # image: cloudbaristaorg/mc-terrarium:0.0.18 # container_name: mc-terrarium # # build: # # context: . diff --git a/go.mod b/go.mod index d49d8b78..261791fe 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/cloud-barista/cb-tumblebug go 1.23.0 require ( - github.com/cloud-barista/mc-terrarium v0.0.16 + github.com/cloud-barista/mc-terrarium v0.0.18 github.com/fsnotify/fsnotify v1.7.0 github.com/go-playground/validator/v10 v10.17.0 github.com/go-resty/resty/v2 v2.13.1 diff --git a/go.sum b/go.sum index a966aab6..c2232faf 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,10 @@ github.com/cloud-barista/mc-terrarium v0.0.15 h1:1yJjCsNMwYZJyYSku9HQPOdj1PwAf5y github.com/cloud-barista/mc-terrarium v0.0.15/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= github.com/cloud-barista/mc-terrarium v0.0.16 h1:0TpSnFk5IpStpQc1YJSbMzB8q1jgVZ639Cjh7YAyxh4= github.com/cloud-barista/mc-terrarium v0.0.16/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= +github.com/cloud-barista/mc-terrarium v0.0.17 h1:W6kyGc2dIKrOMnw0UAOoI66G/bX9Tj/wG4pY9TN9uqI= +github.com/cloud-barista/mc-terrarium v0.0.17/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= +github.com/cloud-barista/mc-terrarium v0.0.18 h1:uDzFOIOoIb7hSXJ5ovrrx8+/hqjHeyNNMU3EDfv8If0= +github.com/cloud-barista/mc-terrarium v0.0.18/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= diff --git a/go.work.sum b/go.work.sum index a33b0bf1..f4123b07 100644 --- a/go.work.sum +++ b/go.work.sum @@ -388,8 +388,7 @@ github.com/cloud-barista/mc-terrarium v0.0.11 h1:qfwie72bzJzYSca24BugO23Iv9aq4gI github.com/cloud-barista/mc-terrarium v0.0.11/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= github.com/cloud-barista/mc-terrarium v0.0.12 h1:A8vGrm7bF6xBwta9uy5BguMEzUUn3X1WvP7xaacJ014= github.com/cloud-barista/mc-terrarium v0.0.12/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= -github.com/cloud-barista/mc-terrarium v0.0.16 h1:0TpSnFk5IpStpQc1YJSbMzB8q1jgVZ639Cjh7YAyxh4= -github.com/cloud-barista/mc-terrarium v0.0.16/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= +github.com/cloud-barista/mc-terrarium v0.0.18/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= diff --git a/src/api/rest/docs/docs.go b/src/api/rest/docs/docs.go index 76ba5fb8..e243e629 100644 --- a/src/api/rest/docs/docs.go +++ b/src/api/rest/docs/docs.go @@ -994,7 +994,8 @@ const docTemplate = `{ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -1058,7 +1059,8 @@ const docTemplate = `{ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -1133,7 +1135,8 @@ const docTemplate = `{ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -7468,6 +7471,262 @@ const docTemplate = `{ } } }, + "/ns/{nsId}/resources/objectStorage": { + "get": { + "description": "Get all Object Storages (TBD)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Get all Object Storages (TBD)", + "operationId": "GetAllObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "enum": [ + "InfoList", + "IdList" + ], + "type": "string", + "default": "IdList", + "description": "Option", + "name": "option", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK\" /////////////", + "schema": { + "$ref": "#/definitions/model.VpnIdList" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + }, + "post": { + "description": "Create a Object Storages\n\nSupported CSPs: AWS, Azure\n- Note - ` + "`" + `connectionName` + "`" + ` example: aws-ap-northeast-2, azure-koreacentral\n\n- Note - Please check the ` + "`" + `requiredCSPResource` + "`" + ` property which includes CSP specific values.\n\n- Note - You can find the API usage examples on this link, https://github.com/cloud-barista/mc-terrarium/discussions/117\n", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Create a Object Storages", + "operationId": "PostObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "description": "Request body to create a Object Storage", + "name": "objectStorageReq", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.RestPostObjectStorageRequest" + } + }, + { + "enum": [ + "retry" + ], + "type": "string", + "description": "Action", + "name": "action", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.ObjectStorageInfo" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + } + }, + "/ns/{nsId}/resources/objectStorage/{objectStorageId}": { + "get": { + "description": "Get resource info of a Object Storage", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Get resource info of a Object Storage", + "operationId": "GetObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "objectstorage01", + "description": "Object Storage ID", + "name": "objectStorageId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "refined", + "description": "Resource info by detail (refined, raw)", + "name": "detail", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.ObjectStorageInfo" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + }, + "delete": { + "description": "Delete a Object Storage", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Delete a Object Storage", + "operationId": "DeleteObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "objectstorage01", + "description": "Object Storage ID", + "name": "objectStorageId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + } + }, "/ns/{nsId}/resources/searchImage": { "post": { "description": "Search image", @@ -8254,7 +8513,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.RestPostSqlDbRequest" + "$ref": "#/definitions/model.RestPostSqlDBRequest" } }, { @@ -10223,7 +10482,8 @@ const docTemplate = `{ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -11989,6 +12249,53 @@ const docTemplate = `{ } } }, + "model.ObjectStorageInfo": { + "type": "object", + "properties": { + "connectionConfig": { + "$ref": "#/definitions/model.ConnConfig" + }, + "connectionName": { + "type": "string" + }, + "cspResourceId": { + "description": "CspResourceId is resource identifier managed by CSP", + "type": "string", + "example": "csp-06eb41e14121c550a" + }, + "cspResourceName": { + "description": "CspResourceName is name assigned to the CSP resource. This name is internally used to handle the resource.", + "type": "string", + "example": "we12fawefadf1221edcf" + }, + "description": { + "type": "string" + }, + "details": {}, + "id": { + "description": "Id is unique identifier for the object", + "type": "string", + "example": "sqldb01" + }, + "name": { + "description": "Name is human-readable string to represent the object", + "type": "string", + "example": "sqldb01" + }, + "resourceType": { + "description": "ResourceType is the type of the resource", + "type": "string" + }, + "status": { + "type": "string" + }, + "uid": { + "description": "Uid is universally unique identifier for the object, used for labelSelector", + "type": "string", + "example": "wef12awefadf1221edcf" + } + } + }, "model.Operation": { "type": "object", "properties": { @@ -12259,7 +12566,7 @@ const docTemplate = `{ } } }, - "model.RequiredAWSResource": { + "model.RequiredAWSResourceForSqlDB": { "type": "object", "properties": { "subnet1ID": { @@ -12276,7 +12583,7 @@ const docTemplate = `{ } } }, - "model.RequiredAzureResource": { + "model.RequiredAzureResourceForObjectStorage": { "type": "object", "properties": { "resourceGroup": { @@ -12285,21 +12592,43 @@ const docTemplate = `{ } } }, - "model.RequiredCSPResource": { + "model.RequiredAzureResourceForSqlDB": { + "type": "object", + "properties": { + "resourceGroup": { + "type": "string", + "example": "koreacentral" + } + } + }, + "model.RequiredCSPResourceForObjectStorage": { + "type": "object", + "properties": { + "azure": { + "description": "AWS RequiredAWSResourceForObjectStorage ` + "`" + `json:\"aws,omitempty\"` + "`" + `", + "allOf": [ + { + "$ref": "#/definitions/model.RequiredAzureResourceForObjectStorage" + } + ] + } + } + }, + "model.RequiredCSPResourceForSqlDB": { "type": "object", "properties": { "aws": { - "$ref": "#/definitions/model.RequiredAWSResource" + "$ref": "#/definitions/model.RequiredAWSResourceForSqlDB" }, "azure": { - "$ref": "#/definitions/model.RequiredAzureResource" + "$ref": "#/definitions/model.RequiredAzureResourceForSqlDB" }, "ncp": { - "$ref": "#/definitions/model.RequiredNCPResource" + "$ref": "#/definitions/model.RequiredNCPResourceForSqlDB" } } }, - "model.RequiredNCPResource": { + "model.RequiredNCPResourceForSqlDB": { "type": "object", "properties": { "subnetID": { @@ -12455,7 +12784,37 @@ const docTemplate = `{ } } }, - "model.RestPostSqlDbRequest": { + "model.RestPostObjectStorageRequest": { + "type": "object", + "required": [ + "connectionName", + "csp", + "name", + "region" + ], + "properties": { + "connectionName": { + "type": "string", + "example": "aws-ap-northeast-2" + }, + "csp": { + "type": "string", + "example": "aws" + }, + "name": { + "type": "string", + "example": "objectstorage01" + }, + "region": { + "type": "string", + "example": "ap-northeast-2" + }, + "requiredCSPResource": { + "$ref": "#/definitions/model.RequiredCSPResourceForObjectStorage" + } + } + }, + "model.RestPostSqlDBRequest": { "type": "object", "required": [ "connectionName", @@ -12506,7 +12865,7 @@ const docTemplate = `{ "example": "ap-northeast-2" }, "requiredCSPResource": { - "$ref": "#/definitions/model.RequiredCSPResource" + "$ref": "#/definitions/model.RequiredCSPResourceForSqlDB" } } }, diff --git a/src/api/rest/docs/swagger.json b/src/api/rest/docs/swagger.json index ffcb285f..6688d94e 100644 --- a/src/api/rest/docs/swagger.json +++ b/src/api/rest/docs/swagger.json @@ -987,7 +987,8 @@ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -1051,7 +1052,8 @@ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -1126,7 +1128,8 @@ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -7461,6 +7464,262 @@ } } }, + "/ns/{nsId}/resources/objectStorage": { + "get": { + "description": "Get all Object Storages (TBD)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Get all Object Storages (TBD)", + "operationId": "GetAllObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "enum": [ + "InfoList", + "IdList" + ], + "type": "string", + "default": "IdList", + "description": "Option", + "name": "option", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK\" /////////////", + "schema": { + "$ref": "#/definitions/model.VpnIdList" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + }, + "post": { + "description": "Create a Object Storages\n\nSupported CSPs: AWS, Azure\n- Note - `connectionName` example: aws-ap-northeast-2, azure-koreacentral\n\n- Note - Please check the `requiredCSPResource` property which includes CSP specific values.\n\n- Note - You can find the API usage examples on this link, https://github.com/cloud-barista/mc-terrarium/discussions/117\n", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Create a Object Storages", + "operationId": "PostObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "description": "Request body to create a Object Storage", + "name": "objectStorageReq", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.RestPostObjectStorageRequest" + } + }, + { + "enum": [ + "retry" + ], + "type": "string", + "description": "Action", + "name": "action", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.ObjectStorageInfo" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + } + }, + "/ns/{nsId}/resources/objectStorage/{objectStorageId}": { + "get": { + "description": "Get resource info of a Object Storage", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Get resource info of a Object Storage", + "operationId": "GetObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "objectstorage01", + "description": "Object Storage ID", + "name": "objectStorageId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "refined", + "description": "Resource info by detail (refined, raw)", + "name": "detail", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.ObjectStorageInfo" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + }, + "delete": { + "description": "Delete a Object Storage", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] Object Storage Management (under development)" + ], + "summary": "Delete a Object Storage", + "operationId": "DeleteObjectStorage", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "objectstorage01", + "description": "Object Storage ID", + "name": "objectStorageId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.SimpleMsg" + } + } + } + } + }, "/ns/{nsId}/resources/searchImage": { "post": { "description": "Search image", @@ -8247,7 +8506,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/model.RestPostSqlDbRequest" + "$ref": "#/definitions/model.RestPostSqlDBRequest" } }, { @@ -10216,7 +10475,8 @@ "securityGroup", "sshKey", "dataDisk", - "sqlDb" + "sqlDb", + "objectStorage" ], "type": "string", "description": "Label Type", @@ -11982,6 +12242,53 @@ } } }, + "model.ObjectStorageInfo": { + "type": "object", + "properties": { + "connectionConfig": { + "$ref": "#/definitions/model.ConnConfig" + }, + "connectionName": { + "type": "string" + }, + "cspResourceId": { + "description": "CspResourceId is resource identifier managed by CSP", + "type": "string", + "example": "csp-06eb41e14121c550a" + }, + "cspResourceName": { + "description": "CspResourceName is name assigned to the CSP resource. This name is internally used to handle the resource.", + "type": "string", + "example": "we12fawefadf1221edcf" + }, + "description": { + "type": "string" + }, + "details": {}, + "id": { + "description": "Id is unique identifier for the object", + "type": "string", + "example": "sqldb01" + }, + "name": { + "description": "Name is human-readable string to represent the object", + "type": "string", + "example": "sqldb01" + }, + "resourceType": { + "description": "ResourceType is the type of the resource", + "type": "string" + }, + "status": { + "type": "string" + }, + "uid": { + "description": "Uid is universally unique identifier for the object, used for labelSelector", + "type": "string", + "example": "wef12awefadf1221edcf" + } + } + }, "model.Operation": { "type": "object", "properties": { @@ -12252,7 +12559,7 @@ } } }, - "model.RequiredAWSResource": { + "model.RequiredAWSResourceForSqlDB": { "type": "object", "properties": { "subnet1ID": { @@ -12269,7 +12576,7 @@ } } }, - "model.RequiredAzureResource": { + "model.RequiredAzureResourceForObjectStorage": { "type": "object", "properties": { "resourceGroup": { @@ -12278,21 +12585,43 @@ } } }, - "model.RequiredCSPResource": { + "model.RequiredAzureResourceForSqlDB": { + "type": "object", + "properties": { + "resourceGroup": { + "type": "string", + "example": "koreacentral" + } + } + }, + "model.RequiredCSPResourceForObjectStorage": { + "type": "object", + "properties": { + "azure": { + "description": "AWS RequiredAWSResourceForObjectStorage `json:\"aws,omitempty\"`", + "allOf": [ + { + "$ref": "#/definitions/model.RequiredAzureResourceForObjectStorage" + } + ] + } + } + }, + "model.RequiredCSPResourceForSqlDB": { "type": "object", "properties": { "aws": { - "$ref": "#/definitions/model.RequiredAWSResource" + "$ref": "#/definitions/model.RequiredAWSResourceForSqlDB" }, "azure": { - "$ref": "#/definitions/model.RequiredAzureResource" + "$ref": "#/definitions/model.RequiredAzureResourceForSqlDB" }, "ncp": { - "$ref": "#/definitions/model.RequiredNCPResource" + "$ref": "#/definitions/model.RequiredNCPResourceForSqlDB" } } }, - "model.RequiredNCPResource": { + "model.RequiredNCPResourceForSqlDB": { "type": "object", "properties": { "subnetID": { @@ -12448,7 +12777,37 @@ } } }, - "model.RestPostSqlDbRequest": { + "model.RestPostObjectStorageRequest": { + "type": "object", + "required": [ + "connectionName", + "csp", + "name", + "region" + ], + "properties": { + "connectionName": { + "type": "string", + "example": "aws-ap-northeast-2" + }, + "csp": { + "type": "string", + "example": "aws" + }, + "name": { + "type": "string", + "example": "objectstorage01" + }, + "region": { + "type": "string", + "example": "ap-northeast-2" + }, + "requiredCSPResource": { + "$ref": "#/definitions/model.RequiredCSPResourceForObjectStorage" + } + } + }, + "model.RestPostSqlDBRequest": { "type": "object", "required": [ "connectionName", @@ -12499,7 +12858,7 @@ "example": "ap-northeast-2" }, "requiredCSPResource": { - "$ref": "#/definitions/model.RequiredCSPResource" + "$ref": "#/definitions/model.RequiredCSPResourceForSqlDB" } } }, diff --git a/src/api/rest/docs/swagger.yaml b/src/api/rest/docs/swagger.yaml index e7babed4..23d44c79 100644 --- a/src/api/rest/docs/swagger.yaml +++ b/src/api/rest/docs/swagger.yaml @@ -745,6 +745,7 @@ paths: - sshKey - dataDisk - sqlDb + - objectStorage - name: uid in: path description: Resource uid @@ -796,6 +797,7 @@ paths: - sshKey - dataDisk - sqlDb + - objectStorage - name: uid in: path description: Resource uid @@ -856,6 +858,7 @@ paths: - sshKey - dataDisk - sqlDb + - objectStorage - name: uid in: path description: Resource uid @@ -5688,6 +5691,216 @@ paths: application/json: schema: $ref: '#/components/schemas/model.SimpleMsg' + /ns/{nsId}/resources/objectStorage: + get: + tags: + - "[Infra Resource] Object Storage Management (under development)" + summary: Get all Object Storages (TBD) + description: Get all Object Storages (TBD) + operationId: GetAllObjectStorage + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: option + in: query + description: Option + schema: + type: string + default: IdList + enum: + - InfoList + - IdList + responses: + "200": + description: OK" ///////////// + content: + application/json: + schema: + $ref: '#/components/schemas/model.VpnIdList' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "503": + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + post: + tags: + - "[Infra Resource] Object Storage Management (under development)" + summary: Create a Object Storages + description: | + Create a Object Storages + + Supported CSPs: AWS, Azure + - Note - `connectionName` example: aws-ap-northeast-2, azure-koreacentral + + - Note - Please check the `requiredCSPResource` property which includes CSP specific values. + + - Note - You can find the API usage examples on this link, https://github.com/cloud-barista/mc-terrarium/discussions/117 + operationId: PostObjectStorage + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: action + in: query + description: Action + schema: + type: string + enum: + - retry + requestBody: + description: Request body to create a Object Storage + content: + application/json: + schema: + $ref: '#/components/schemas/model.RestPostObjectStorageRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/model.ObjectStorageInfo' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "503": + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + x-codegen-request-body-name: objectStorageReq + /ns/{nsId}/resources/objectStorage/{objectStorageId}: + get: + tags: + - "[Infra Resource] Object Storage Management (under development)" + summary: Get resource info of a Object Storage + description: Get resource info of a Object Storage + operationId: GetObjectStorage + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: objectStorageId + in: path + description: Object Storage ID + required: true + schema: + type: string + default: objectstorage01 + - name: detail + in: query + description: "Resource info by detail (refined, raw)" + schema: + type: string + default: refined + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/model.ObjectStorageInfo' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "503": + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + delete: + tags: + - "[Infra Resource] Object Storage Management (under development)" + summary: Delete a Object Storage + description: Delete a Object Storage + operationId: DeleteObjectStorage + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: objectStorageId + in: path + description: Object Storage ID + required: true + schema: + type: string + default: objectstorage01 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' + "503": + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/model.SimpleMsg' /ns/{nsId}/resources/searchImage: post: tags: @@ -6292,7 +6505,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/model.RestPostSqlDbRequest' + $ref: '#/components/schemas/model.RestPostSqlDBRequest' required: true responses: "200": @@ -7789,6 +8002,7 @@ paths: - sshKey - dataDisk - sqlDb + - objectStorage - name: labelSelector in: query description: "Label selector query. Example: env=production,tier=backend" @@ -9010,6 +9224,44 @@ components: name: type: string example: default + model.ObjectStorageInfo: + type: object + properties: + connectionConfig: + $ref: '#/components/schemas/model.ConnConfig' + connectionName: + type: string + cspResourceId: + type: string + description: CspResourceId is resource identifier managed by CSP + example: csp-06eb41e14121c550a + cspResourceName: + type: string + description: CspResourceName is name assigned to the CSP resource. This + name is internally used to handle the resource. + example: we12fawefadf1221edcf + description: + type: string + details: + type: object + id: + type: string + description: Id is unique identifier for the object + example: sqldb01 + name: + type: string + description: Name is human-readable string to represent the object + example: sqldb01 + resourceType: + type: string + description: ResourceType is the type of the resource + status: + type: string + uid: + type: string + description: "Uid is universally unique identifier for the object, used\ + \ for labelSelector" + example: wef12awefadf1221edcf model.Operation: type: object properties: @@ -9192,7 +9444,7 @@ components: type: integer vm: type: integer - model.RequiredAWSResource: + model.RequiredAWSResourceForSqlDB: type: object properties: subnet1ID: @@ -9204,22 +9456,37 @@ components: vNetID: type: string example: vpc-xxxxx - model.RequiredAzureResource: + model.RequiredAzureResourceForObjectStorage: + type: object + properties: + resourceGroup: + type: string + example: koreacentral + model.RequiredAzureResourceForSqlDB: type: object properties: resourceGroup: type: string example: koreacentral - model.RequiredCSPResource: + model.RequiredCSPResourceForObjectStorage: + type: object + properties: + azure: + type: object + description: "AWS RequiredAWSResourceForObjectStorage `json:\"aws,omitempty\"\ + `" + allOf: + - $ref: '#/components/schemas/model.RequiredAzureResourceForObjectStorage' + model.RequiredCSPResourceForSqlDB: type: object properties: aws: - $ref: '#/components/schemas/model.RequiredAWSResource' + $ref: '#/components/schemas/model.RequiredAWSResourceForSqlDB' azure: - $ref: '#/components/schemas/model.RequiredAzureResource' + $ref: '#/components/schemas/model.RequiredAzureResourceForSqlDB' ncp: - $ref: '#/components/schemas/model.RequiredNCPResource' - model.RequiredNCPResource: + $ref: '#/components/schemas/model.RequiredNCPResourceForSqlDB' + model.RequiredNCPResourceForSqlDB: type: object properties: subnetID: @@ -9323,7 +9590,29 @@ components: success: type: boolean example: true - model.RestPostSqlDbRequest: + model.RestPostObjectStorageRequest: + required: + - connectionName + - csp + - name + - region + type: object + properties: + connectionName: + type: string + example: aws-ap-northeast-2 + csp: + type: string + example: aws + name: + type: string + example: objectstorage01 + region: + type: string + example: ap-northeast-2 + requiredCSPResource: + $ref: '#/components/schemas/model.RequiredCSPResourceForObjectStorage' + model.RestPostSqlDBRequest: required: - connectionName - csp @@ -9364,7 +9653,7 @@ components: type: string example: ap-northeast-2 requiredCSPResource: - $ref: '#/components/schemas/model.RequiredCSPResource' + $ref: '#/components/schemas/model.RequiredCSPResourceForSqlDB' model.RestPostVpnRequest: required: - name diff --git a/src/api/rest/server/common/label/label.go b/src/api/rest/server/common/label/label.go index 347cd30b..e8fc704c 100644 --- a/src/api/rest/server/common/label/label.go +++ b/src/api/rest/server/common/label/label.go @@ -31,7 +31,7 @@ import ( // @Tags [Infra Resource] Common Utility // @Accept json // @Produce json -// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb, objectStorage) // @Param uid path string true "Resource uid" // @Param labels body model.Label true "Labels to create or update" // @Success 200 {object} model.SimpleMsg "Label created or updated successfully" @@ -68,7 +68,7 @@ func RestCreateOrUpdateLabel(c echo.Context) error { // @Tags [Infra Resource] Common Utility // @Accept json // @Produce json -// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb, objectStorage) // @Param uid path string true "Resource uid" // @Param key path string true "Label key to remove" // @Success 200 {object} model.SimpleMsg "Label removed successfully" @@ -97,7 +97,7 @@ func RestRemoveLabel(c echo.Context) error { // @Tags [Infra Resource] Common Utility // @Accept json // @Produce json -// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb, objectStorage) // @Param uid path string true "Resource uid" // @Success 200 {object} model.LabelInfo "Labels for the resource" // @Failure 400 {object} model.SimpleMsg "Invalid request" @@ -135,7 +135,7 @@ type ResourcesResponse struct { // @Tags [Infra Resource] Common Utility // @Accept json // @Produce json -// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb, objectStorage) // @Param labelSelector query string true "Label selector query. Example: env=production,tier=backend" // @Success 200 {object} ResourcesResponse "Matched resources" // @Failure 400 {object} model.SimpleMsg "Invalid request" diff --git a/src/api/rest/server/resource/objectStorage.go b/src/api/rest/server/resource/objectStorage.go new file mode 100644 index 00000000..e1877216 --- /dev/null +++ b/src/api/rest/server/resource/objectStorage.go @@ -0,0 +1,622 @@ +/* +Copyright 2019 The Cloud-Barista Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package mci is to handle REST API for mci +package resource + +import ( + "fmt" + "net/http" + "strings" + + "github.com/cloud-barista/cb-tumblebug/src/core/common" + "github.com/cloud-barista/cb-tumblebug/src/core/model" + "github.com/cloud-barista/cb-tumblebug/src/core/resource" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" +) + +// // RestGetSitesInMci godoc +// // @ID GetSitesInMci +// // @Summary Get sites in MCI +// // @Description Get sites in MCI +// // @Tags [Infra Resource] Object Storage Management (under development) +// // @Accept json +// // @Produce json +// // @Param nsId path string true "Namespace ID" default(default) +// // @Param mciId path string true "MCI ID" default(mci01) +// // @Success 200 {object} model.SitesInfo "OK" +// // @Failure 400 {object} model.SimpleMsg "Bad Request" +// // @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// // @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// // @Router /ns/{nsId}/mci/{mciId}/site [get] +// func RestGetSitesInMci(c echo.Context) error { + +// nsId := c.Param("nsId") +// err := common.CheckString(nsId) +// if err != nil { +// errMsg := fmt.Errorf("invalid nsId (%s)", nsId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } + +// mciId := c.Param("mciId") +// err = common.CheckString(mciId) +// if err != nil { +// errMsg := fmt.Errorf("invalid mciId (%s)", mciId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } + +// SitesInfo, err := ExtractSitesInfoFromMciInfo(nsId, mciId) +// if err != nil { +// log.Err(err).Msg("") +// res := model.SimpleMsg{ +// Message: err.Error(), +// } +// return c.JSON(http.StatusInternalServerError, res) +// } + +// return c.JSON(http.StatusOK, SitesInfo) +// } + +// func ExtractSitesInfoFromMciInfo(nsId, mciId string) (*model.SitesInfo, error) { +// // Get MCI info +// mciInfo, err := infra.GetMciInfo(nsId, mciId) +// if err != nil { +// log.Err(err).Msg("") +// return nil, err +// } + +// // A map to check if the VPC (site) is already extracted and added or not. +// checkedVpcs := make(map[string]bool) + +// // Newly create the SitesInfo structure +// sitesInfo := model.NewSiteInfo(nsId, mciId) + +// sitesInAws := []model.SiteDetail{} +// sitesInAzure := []model.SiteDetail{} +// sitesInGcp := []model.SiteDetail{} + +// for _, vm := range mciInfo.Vm { + +// vNetId := vm.VNetId +// if vNetId == "" { +// log.Warn().Msgf("VNet ID is empty for VM ID: %s", vm.Id) +// continue +// } + +// if _, exists := checkedVpcs[vNetId]; exists { +// continue +// } +// checkedVpcs[vNetId] = true + +// providerName := vm.ConnectionConfig.ProviderName +// if providerName == "" { +// log.Warn().Msgf("Provider name is empty for VM ID: %s", vm.Id) +// continue +// } + +// // Create and set a site details +// var site = model.SiteDetail{} +// site.CSP = vm.ConnectionConfig.ProviderName +// site.Region = vm.Region.Region + +// // Lowercase the provider name +// providerName = strings.ToLower(providerName) + +// switch providerName { +// case "aws": + +// // Get vNet info +// resourceType := "vNet" +// resourceId := vm.VNetId +// result, err := resource.GetResource(nsId, resourceType, resourceId) +// if err != nil { +// log.Warn().Msgf("Failed to get the VNet info for ID: %s", resourceId) +// continue +// } +// vNetInfo := result.(model.TbVNetInfo) + +// // Get the last subnet +// subnetCount := len(vNetInfo.SubnetInfoList) +// if subnetCount == 0 { +// log.Warn().Msgf("No subnets found for VNet ID: %s", vNetId) +// continue +// } +// lastSubnet := vNetInfo.SubnetInfoList[subnetCount-1] + +// // Set VNet and the last subnet IDs +// site.VNet = vm.CspVNetId +// site.Subnet = lastSubnet.CspResourceId + +// // Set connection name +// site.ConnectionName = vm.ConnectionName + +// sitesInAws = append(sitesInAws, site) + +// case "azure": +// // Parse vNet and resource group names +// parts := strings.Split(vm.CspVNetId, "/") +// log.Debug().Msgf("parts: %+v", parts) +// if len(parts) < 9 { +// log.Warn().Msgf("Invalid VNet ID format for Azure VM ID: %s", vm.Id) +// continue +// } +// parsedResourceGroupName := parts[4] +// parsedVirtualNetworkName := parts[8] + +// // Set VNet and resource group names +// site.VNet = parsedVirtualNetworkName +// site.ResourceGroup = parsedResourceGroupName + +// // Get vNet info +// resourceType := "vNet" +// resourceId := vm.VNetId +// result, err := resource.GetResource(nsId, resourceType, resourceId) +// if err != nil { +// log.Warn().Msgf("Failed to get the VNet info for ID: %s", resourceId) +// continue +// } +// vNetInfo := result.(model.TbVNetInfo) + +// // Get the last subnet CIDR block +// subnetCount := len(vNetInfo.SubnetInfoList) +// if subnetCount == 0 { +// log.Warn().Msgf("No subnets found for VNet ID: %s", vNetId) +// continue +// } +// lastSubnet := vNetInfo.SubnetInfoList[subnetCount-1] +// lastSubnetCidr := lastSubnet.IPv4_CIDR + +// // (Currently unsafe) Calculate the next subnet CIDR block +// nextCidr, err := netutil.NextSubnet(lastSubnetCidr, vNetInfo.CidrBlock) +// if err != nil { +// log.Warn().Msgf("Failed to get the next subnet CIDR") +// } + +// // Set the site detail +// site.GatewaySubnetCidr = nextCidr + +// // Set connection name +// site.ConnectionName = vm.ConnectionName + +// sitesInAzure = append(sitesInAzure, site) + +// case "gcp": +// // Set vNet ID +// site.VNet = vm.CspVNetId + +// // Set connection name +// site.ConnectionName = vm.ConnectionName + +// sitesInGcp = append(sitesInGcp, site) + +// default: +// log.Warn().Msgf("Unsupported provider name: %s", providerName) +// } + +// sitesInfo.Count++ +// } + +// sitesInfo.Sites.Aws = sitesInAws +// sitesInfo.Sites.Azure = sitesInAzure +// sitesInfo.Sites.Gcp = sitesInGcp + +// return sitesInfo, nil +// } + +// RestGetAllObjectStorage godoc +// @ID GetAllObjectStorage +// @Summary Get all Object Storages (TBD) +// @Description Get all Object Storages (TBD) +// @Tags [Infra Resource] Object Storage Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param option query string false "Option" Enums(InfoList, IdList) default(IdList) +// @Success 200 {object} model.VpnInfoList "OK" ///////////// +// @Success 200 {object} model.VpnIdList "OK" ///////////// +// @Failure 400 {object} model.SimpleMsg "Bad Request" +// @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// @Router /ns/{nsId}/resources/objectStorage [get] +func RestGetAllObjectStorage(c echo.Context) error { + + nsId := c.Param("nsId") + err := common.CheckString(nsId) + if err != nil { + errMsg := fmt.Errorf("invalid nsId (%s)", nsId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + // mciId := c.Param("mciId") + // err = common.CheckString(mciId) + // if err != nil { + // errMsg := fmt.Errorf("invalid mciId (%s)", mciId) + // log.Warn().Err(err).Msgf(errMsg.Error()) + // return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + // } + + option := c.QueryParam("option") + if option != "InfoList" && option != "IdList" && option != "" { + errMsg := fmt.Errorf("invalid option (%s)", option) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + // switch option { + // case "InfoList": + // vpnInfoList, err := resource.GetAllSiteToSiteVPN(nsId, mciId) + // if err != nil { + // log.Err(err).Msg("") + // return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + // } + // return c.JSON(http.StatusOK, vpnInfoList) + // case "IdList": + // vpnIdList, err := resource.GetAllIDsOfSiteToSiteVPN(nsId, mciId) + // if err != nil { + // log.Err(err).Msg("") + // return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + // } + // return c.JSON(http.StatusOK, vpnIdList) + // default: + // errMsg := fmt.Errorf("invalid option (%s)", option) + // log.Warn().Err(err).Msgf(errMsg.Error()) + // return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + // } + + return c.JSON(http.StatusOK, model.VpnInfoList{}) +} + +// RestPostObjectStorage godoc +// @ID PostObjectStorage +// @Summary Create a Object Storages +// @Description Create a Object Storages +// @Description +// @Description Supported CSPs: AWS, Azure +// @Description - Note - `connectionName` example: aws-ap-northeast-2, azure-koreacentral +// @Description +// @Description - Note - Please check the `requiredCSPResource` property which includes CSP specific values. +// @Description +// @Description - Note - You can find the API usage examples on this link, https://github.com/cloud-barista/mc-terrarium/discussions/117 +// @Description +// @Tags [Infra Resource] Object Storage Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param objectStorageReq body model.RestPostObjectStorageRequest true "Request body to create a Object Storage" +// @Param action query string false "Action" Enums(retry) +// @Success 200 {object} model.ObjectStorageInfo "OK" +// @Failure 400 {object} model.SimpleMsg "Bad Request" +// @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// @Router /ns/{nsId}/resources/objectStorage [post] +func RestPostObjectStorage(c echo.Context) error { + + nsId := c.Param("nsId") + err := common.CheckString(nsId) + if err != nil { + errMsg := fmt.Errorf("invalid nsId (%s)", nsId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + action := c.QueryParam("action") + if action != "retry" && action != "" { + errMsg := fmt.Errorf("invalid action (%s)", action) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + // Bind the request body to RestPostObjectStorageRequest struct + objectStorageReq := new(model.RestPostObjectStorageRequest) + if err := c.Bind(objectStorageReq); err != nil { + log.Warn().Err(err).Msgf("") + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: err.Error()}) + } + + // Validate the CSP is supported + objectStorageReq.CSP = strings.ToLower(objectStorageReq.CSP) + ok, err := resource.IsValidCspForObjectStorage(objectStorageReq.CSP) + if !ok { + log.Warn().Err(err).Msg("") + res := model.SimpleMsg{ + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + err = common.CheckString(objectStorageReq.Name) + if err != nil { + errMsg := fmt.Errorf("invalid objectStorageName (%s)", objectStorageReq.Name) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + var resp model.ObjectStorageInfo + resp, err = resource.CreateObjectStorage(nsId, objectStorageReq, action) + if err != nil { + log.Err(err).Msg("") + return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + } + + return c.JSON(http.StatusOK, resp) + +} + +// RestGetObjectStorage godoc +// @ID GetObjectStorage +// @Summary Get resource info of a Object Storage +// @Description Get resource info of a Object Storage +// @Tags [Infra Resource] Object Storage Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param objectStorageId path string true "Object Storage ID" default(objectstorage01) +// @Param detail query string false "Resource info by detail (refined, raw)" default(refined) +// @Success 200 {object} model.ObjectStorageInfo "OK" +// @Failure 400 {object} model.SimpleMsg "Bad Request" +// @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// @Router /ns/{nsId}/resources/objectStorage/{objectStorageId} [get] +func RestGetObjectStorage(c echo.Context) error { + + nsId := c.Param("nsId") + err := common.CheckString(nsId) + if err != nil { + errMsg := fmt.Errorf("invalid nsId (%s)", nsId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + objectStorageId := c.Param("objectStorageId") + err = common.CheckString(objectStorageId) + if err != nil { + errMsg := fmt.Errorf("invalid objectStorageId (%s)", objectStorageId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + // // Use this struct like the enum + // var DetailOptions = struct { + // Refined string + // Raw string + // }{ + // Refined: "refined", + // Raw: "raw", + // } + + // // valid detail options + // validDetailOptions := map[string]bool{ + // DetailOptions.Refined: true, + // DetailOptions.Raw: true, + // } + + // detail := c.QueryParam("detail") + // detail = strings.ToLower(detail) + + // if detail == "" || !validDetailOptions[detail] { + // err := fmt.Errorf("invalid detail (%s), use the default (%s)", detail, DetailOptions.Refined) + // log.Warn().Msg(err.Error()) + // detail = DetailOptions.Refined + // } + + var resp model.ObjectStorageInfo + // currently, only support detail=refined + detail := "refined" + resp, err = resource.GetObjectStorage(nsId, objectStorageId, detail) + if err != nil { + log.Err(err).Msg("") + return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + } + + return c.JSON(http.StatusOK, resp) +} + +// RestDeleteObjectStorage godoc +// @ID DeleteObjectStorage +// @Summary Delete a Object Storage +// @Description Delete a Object Storage +// @Tags [Infra Resource] Object Storage Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param objectStorageId path string true "Object Storage ID" default(objectstorage01) +// @Success 200 {object} model.SimpleMsg "OK" +// @Failure 400 {object} model.SimpleMsg "Bad Request" +// @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// @Router /ns/{nsId}/resources/objectStorage/{objectStorageId} [delete] +func RestDeleteObjectStorage(c echo.Context) error { + + nsId := c.Param("nsId") + err := common.CheckString(nsId) + if err != nil { + errMsg := fmt.Errorf("invalid nsId (%s)", nsId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + objectStorageId := c.Param("objectStorageId") + err = common.CheckString(objectStorageId) + if err != nil { + errMsg := fmt.Errorf("invalid objectStorageId (%s)", objectStorageId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + resp, err := resource.DeleteObjectStorage(nsId, objectStorageId) + if err != nil { + log.Err(err).Msg("") + return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + } + + return c.JSON(http.StatusOK, resp) +} + +// // RestPutObjectStorage godoc +// // @ID PutObjectStorage +// // @Summary (To be provided) Update the Object Storage +// // @Description (To be provided) Update the Object Storage +// // @Tags [Infra Resource] Object Storage Management (under development) +// // @Accept json +// // @Produce json-stream +// // @Param nsId path string true "Namespace ID" default(default) +//// // @Param mciId path string true "MCI ID" default(mci01) +// // @Param vpnId path string true "Object Storage ID" default(objectstorage01) +// // @Param vpnReq body model.RestPostVpnRequest true "Resources info for VPN tunnel configuration between GCP and AWS" +// // @Success 200 {object} model.SimpleMsg "OK" +// // @Failure 400 {object} model.SimpleMsg "Bad Request" +// // @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// // @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// // @Router /ns/{nsId}/resources/qqlDb/{ObjectStorageId} [put] +// func RestPutObjectStorage(c echo.Context) error { + +// nsId := c.Param("nsId") +// err := common.CheckString(nsId) +// if err != nil { +// errMsg := fmt.Errorf("invalid nsId (%s)", nsId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } + +// mciId := c.Param("mciId") +// err = common.CheckString(mciId) +// if err != nil { +// errMsg := fmt.Errorf("invalid mciId (%s)", mciId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } + +// vpnId := c.Param("vpnId") +// err = common.CheckString(vpnId) +// if err != nil { +// errMsg := fmt.Errorf("invalid vpnId (%s)", vpnId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } + +// // Prepare for streaming response +// c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) +// c.Response().WriteHeader(http.StatusOK) +// enc := json.NewEncoder(c.Response()) + +// // Flush a response +// res := model.SimpleMsg{ +// Message: "note - API to be provided", +// } +// if err := enc.Encode(res); err != nil { +// return err +// } +// c.Response().Flush() + +// return nil + +// // Initialize resty client with basic auth +// // client := resty.New() +// // apiUser := os.Getenv("TB_API_USERNAME") +// // apiPass := os.Getenv("TB_API_PASSWORD") +// // client.SetBasicAuth(apiUser, apiPass) + +// // epTerrarium := "http://localhost:8055/terrarium" +// // trId := fmt.Sprintf("%s-%s-%s", nsId, mciId, vpnId) + +// // // check readyz +// // method := "GET" +// // url := fmt.Sprintf("%s/readyz", epTerrarium) +// // requestBody := common.NoBody +// // resReadyz := new(model.Response) + +// // err := common.ExecuteHttpRequest( +// // client, +// // method, +// // url, +// // nil, +// // common.SetUseBody(requestBody), +// // &requestBody, +// // resReadyz, +// // common.VeryShortDuration, +// // ) + +// // if err != nil { +// // log.Err(err).Msg("") +// // res := model.SimpleMsg{ +// // Message: err.Error(), +// // } +// // return c.JSON(http.StatusServiceUnavailable, res) +// // } +// // log.Debug().Msgf("resReadyz: %+v", resReadyz) + +// // // Flush a response +// // res := model.SimpleMsg{ +// // Message: resReadyz.Message, +// // } +// // if err := enc.Encode(res); err != nil { +// // return err +// // } +// // c.Response().Flush() + +// // return nil +// } + +// // RestGetRequestStatusOfObjectStorage godoc +// // @ID GetRequestStatusOfObjectStorage +// // @Summary Check the status of a specific request by its ID +// // @Description Check the status of a specific request by its ID +// // @Tags [Infra Resource] Object Storage Management (under development) +// // @Accept json +// // @Produce json +// // @Param nsId path string true "Namespace ID" default(default) +// // @Param objectStorageId path string true "Object Storage ID" default(objectstorage01) +// // @Param requestId path string true "Request ID" +// // @Success 200 {object} model.Response "OK" +// // @Failure 400 {object} model.SimpleMsg "Bad Request" +// // @Failure 500 {object} model.SimpleMsg "Internal Server Error" +// // @Failure 503 {object} model.SimpleMsg "Service Unavailable" +// // @Router /ns/{nsId}/resources/objectStorage/{objectStorageId}/request/{requestId} [get] +// func RestGetRequestStatusOfObjectStorage(c echo.Context) error { + +// nsId := c.Param("nsId") +// err := common.CheckString(nsId) +// if err != nil { +// errMsg := fmt.Errorf("invalid nsId (%s)", nsId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } + +// objectStorageId := c.Param("objectStorageId") +// err = common.CheckString(objectStorageId) +// if err != nil { +// errMsg := fmt.Errorf("invalid objectStorageId (%s)", objectStorageId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } +// reqId := c.Param("requestId") +// if reqId == "" { +// errMsg := fmt.Errorf("invalid reqId (%s)", reqId) +// log.Warn().Err(err).Msgf(errMsg.Error()) +// return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) +// } +// reqId = strings.TrimSpace(reqId) + +// var resp model.Response +// resp, err = resource.GetRequestStatusOfObjectStorage(nsId, objectStorageId, reqId) +// if err != nil { +// log.Err(err).Msg("") +// return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) +// } + +// return c.JSON(http.StatusOK, resp) +// } diff --git a/src/api/rest/server/resource/sqlDb.go b/src/api/rest/server/resource/sqlDb.go index f45e7dec..43916afe 100644 --- a/src/api/rest/server/resource/sqlDb.go +++ b/src/api/rest/server/resource/sqlDb.go @@ -231,7 +231,7 @@ import ( // @Failure 500 {object} model.SimpleMsg "Internal Server Error" // @Failure 503 {object} model.SimpleMsg "Service Unavailable" // @Router /ns/{nsId}/resources/sqlDb [get] -func RestGetAllSqlDb(c echo.Context) error { +func RestGetAllSqlDB(c echo.Context) error { nsId := c.Param("nsId") err := common.CheckString(nsId) @@ -280,7 +280,7 @@ func RestGetAllSqlDb(c echo.Context) error { return c.JSON(http.StatusOK, model.VpnInfoList{}) } -// RestPostSqlDb godoc +// RestPostSqlDB godoc // @ID PostSqlDb // @Summary Create a SQL Databases // @Description Create a SQL Databases @@ -296,14 +296,14 @@ func RestGetAllSqlDb(c echo.Context) error { // @Accept json // @Produce json // @Param nsId path string true "Namespace ID" default(default) -// @Param sqlDbReq body model.RestPostSqlDbRequest true "Request body to create a SQL database" +// @Param sqlDbReq body model.RestPostSqlDBRequest true "Request body to create a SQL database" // @Param action query string false "Action" Enums(retry) // @Success 200 {object} model.SqlDBInfo "OK" // @Failure 400 {object} model.SimpleMsg "Bad Request" // @Failure 500 {object} model.SimpleMsg "Internal Server Error" // @Failure 503 {object} model.SimpleMsg "Service Unavailable" // @Router /ns/{nsId}/resources/sqlDb [post] -func RestPostSqlDb(c echo.Context) error { +func RestPostSqlDB(c echo.Context) error { nsId := c.Param("nsId") err := common.CheckString(nsId) @@ -321,7 +321,7 @@ func RestPostSqlDb(c echo.Context) error { } // Bind the request body to RestPostSqlDbRequest struct - sqlDbReq := new(model.RestPostSqlDbRequest) + sqlDbReq := new(model.RestPostSqlDBRequest) if err := c.Bind(sqlDbReq); err != nil { log.Warn().Err(err).Msgf("") return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: err.Error()}) @@ -346,7 +346,7 @@ func RestPostSqlDb(c echo.Context) error { } var resp model.SqlDBInfo - resp, err = resource.CreateSqlDb(nsId, sqlDbReq, action) + resp, err = resource.CreateSqlDB(nsId, sqlDbReq, action) if err != nil { log.Err(err).Msg("") return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) @@ -356,7 +356,7 @@ func RestPostSqlDb(c echo.Context) error { } -// RestGetSqlDb godoc +// RestGetSqlDB godoc // @ID GetSqlDb // @Summary Get resource info of a SQL datatbase // @Description Get resource info of a SQL datatbase @@ -371,7 +371,7 @@ func RestPostSqlDb(c echo.Context) error { // @Failure 500 {object} model.SimpleMsg "Internal Server Error" // @Failure 503 {object} model.SimpleMsg "Service Unavailable" // @Router /ns/{nsId}/resources/sqlDb/{sqlDbId} [get] -func RestGetSqlDb(c echo.Context) error { +func RestGetSqlDB(c echo.Context) error { nsId := c.Param("nsId") err := common.CheckString(nsId) @@ -416,7 +416,7 @@ func RestGetSqlDb(c echo.Context) error { var resp model.SqlDBInfo // currently, only support detail=refined detail := "refined" - resp, err = resource.GetSqlDb(nsId, sqlDbId, detail) + resp, err = resource.GetSqlDB(nsId, sqlDbId, detail) if err != nil { log.Err(err).Msg("") return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) @@ -425,7 +425,7 @@ func RestGetSqlDb(c echo.Context) error { return c.JSON(http.StatusOK, resp) } -// RestDeleteSqlDb godoc +// RestDeleteSqlDB godoc // @ID DeleteSqlDb // @Summary Delete a SQL datatbase // @Description Delete a SQL datatbase @@ -439,7 +439,7 @@ func RestGetSqlDb(c echo.Context) error { // @Failure 500 {object} model.SimpleMsg "Internal Server Error" // @Failure 503 {object} model.SimpleMsg "Service Unavailable" // @Router /ns/{nsId}/resources/sqlDb/{sqlDbId} [delete] -func RestDeleteSqlDb(c echo.Context) error { +func RestDeleteSqlDB(c echo.Context) error { nsId := c.Param("nsId") err := common.CheckString(nsId) @@ -457,7 +457,7 @@ func RestDeleteSqlDb(c echo.Context) error { return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) } - resp, err := resource.DeleteSqlDb(nsId, sqlDbId) + resp, err := resource.DeleteSqlDB(nsId, sqlDbId) if err != nil { log.Err(err).Msg("") return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) @@ -481,8 +481,8 @@ func RestDeleteSqlDb(c echo.Context) error { // // @Failure 400 {object} model.SimpleMsg "Bad Request" // // @Failure 500 {object} model.SimpleMsg "Internal Server Error" // // @Failure 503 {object} model.SimpleMsg "Service Unavailable" -// // @Router /ns/{nsId}/resources/qqlDb/{SqlDbId} [put] -// func RestPutSqlDb(c echo.Context) error { +// // @Router /ns/{nsId}/resources/sqlDb/{SqlDbId} [put] +// func RestPutSqlDB(c echo.Context) error { // nsId := c.Param("nsId") // err := common.CheckString(nsId) @@ -586,7 +586,7 @@ func RestDeleteSqlDb(c echo.Context) error { // // @Failure 500 {object} model.SimpleMsg "Internal Server Error" // // @Failure 503 {object} model.SimpleMsg "Service Unavailable" // // @Router /ns/{nsId}/resources/sqlDb/{sqlDbId}/request/{requestId} [get] -// func RestGetRequestStatusOfSqlDb(c echo.Context) error { +// func RestGetRequestStatusOfSqlDB(c echo.Context) error { // nsId := c.Param("nsId") // err := common.CheckString(nsId) @@ -612,7 +612,7 @@ func RestDeleteSqlDb(c echo.Context) error { // reqId = strings.TrimSpace(reqId) // var resp model.Response -// resp, err = resource.GetRequestStatusOfSqlDb(nsId, sqlDbId, reqId) +// resp, err = resource.GetRequestStatusOfSqlDB(nsId, sqlDbId, reqId) // if err != nil { // log.Err(err).Msg("") // return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) diff --git a/src/api/rest/server/server.go b/src/api/rest/server/server.go index b154cf50..cc66f8da 100644 --- a/src/api/rest/server/server.go +++ b/src/api/rest/server/server.go @@ -501,11 +501,22 @@ func RunServer() { sqlDbGroup := g.Group("/:nsId/resources/sqlDb") terrariumURL = model.TerrariumRestUrl + "/readyz" sqlDbGroup.Use(middlewares.CheckReadiness(terrariumURL, apiUser, apiPass)) - sqlDbGroup.POST("", rest_resource.RestPostSqlDb) - sqlDbGroup.GET("/:sqlDbId", rest_resource.RestGetSqlDb) - sqlDbGroup.DELETE("/:sqlDbId", rest_resource.RestDeleteSqlDb) - // sqlDbGroup.GET("/:sqlDbId/request/:requestId", rest_resource.RestGetRequestStatusOfSqlDb) - // sqlDbGroup.PUT("//:sqlDbId", rest_resource.RestPutSqlDs) + sqlDbGroup.POST("", rest_resource.RestPostSqlDB) + sqlDbGroup.GET("/:sqlDbId", rest_resource.RestGetSqlDB) + sqlDbGroup.DELETE("/:sqlDbId", rest_resource.RestDeleteSqlDB) + // sqlDbGroup.GET("/:sqlDbId/request/:requestId", rest_resource.RestGetRequestStatusOfSqlDB) + // sqlDbGroup.PUT("//:sqlDbId", rest_resource.RestPutSqlDB) + + // Object Storage management + // g.GET("/:nsId/resources/objectStorage", rest_resource.) + objectStorageGroup := g.Group("/:nsId/resources/objectStorage") + terrariumURL = model.TerrariumRestUrl + "/readyz" + objectStorageGroup.Use(middlewares.CheckReadiness(terrariumURL, apiUser, apiPass)) + objectStorageGroup.POST("", rest_resource.RestPostObjectStorage) + objectStorageGroup.GET("/:objectStorageId", rest_resource.RestGetObjectStorage) + objectStorageGroup.DELETE("/:objectStorageId", rest_resource.RestDeleteObjectStorage) + // objectStorageGroup.GET("/:objectStorageId/request/:requestId", rest_resource.RestGetRequestStatusOfObjectStorage) + // objectStorageGroup.PUT("//:objectStorageId", rest_resource.RestPutObjectStorage) /* g.POST("/:nsId/resources/publicIp", resource.RestPostPublicIp) diff --git a/src/core/common/utility.go b/src/core/common/utility.go index 72183d31..b25e47ab 100644 --- a/src/core/common/utility.go +++ b/src/core/common/utility.go @@ -223,6 +223,7 @@ func GenResourceKey(nsId string, resourceType string, resourceId string) string resourceType == model.StrVNet || resourceType == model.StrVPN || resourceType == model.StrSqlDB || + resourceType == model.StrObjectStorage || resourceType == model.StrSecurityGroup || resourceType == model.StrDataDisk { //resourceType == "publicIp" || diff --git a/src/core/model/common.go b/src/core/model/common.go index 3a932487..6b5bc6fc 100644 --- a/src/core/model/common.go +++ b/src/core/model/common.go @@ -101,6 +101,7 @@ const ( StrSubnet string = "subnet" StrVPN string = "vpn" StrSqlDB string = "sqlDb" + StrObjectStorage string = "objectStorage" StrDataDisk string = "dataDisk" StrNLB string = "nlb" StrVM string = "vm" diff --git a/src/core/model/objectStorage.go b/src/core/model/objectStorage.go new file mode 100644 index 00000000..59675e3c --- /dev/null +++ b/src/core/model/objectStorage.go @@ -0,0 +1,58 @@ +/* +Copyright 2019 The Cloud-Barista Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package mci is to handle REST API for mci +package model + +type RestPostObjectStorageRequest struct { + Name string `json:"name" validate:"required" example:"objectstorage01"` + ConnectionName string `json:"connectionName" validate:"required" example:"aws-ap-northeast-2"` + CSP string `json:"csp" validate:"required" example:"aws"` + Region string `json:"region" validate:"required" example:"ap-northeast-2"` + RequiredCSPResource RequiredCSPResourceForObjectStorage `json:"requiredCSPResource,omitempty"` +} + +type RequiredCSPResourceForObjectStorage struct { + // AWS RequiredAWSResourceForObjectStorage `json:"aws,omitempty"` + Azure RequiredAzureResourceForObjectStorage `json:"azure,omitempty"` +} + +// type RequiredAWSResourceForObjectStorage struct { +// VNetID string `json:"vNetID,omitempty" example:"vpc-xxxxx"` +// Subnet1ID string `json:"subnet1ID,omitempty" example:"subnet-xxxx"` +// Subnet2ID string `json:"subnet2ID,omitempty" example:"subnet-xxxx in different AZ"` +// } + +type RequiredAzureResourceForObjectStorage struct { + ResourceGroup string `json:"resourceGroup,omitempty" example:"koreacentral"` +} + +type ObjectStorageInfo struct { + // ResourceType is the type of the resource + ResourceType string `json:"resourceType"` + ConnectionName string `json:"connectionName"` + ConnectionConfig ConnConfig `json:"connectionConfig"` + // Id is unique identifier for the object + Id string `json:"id" example:"sqldb01"` + // Uid is universally unique identifier for the object, used for labelSelector + Uid string `json:"uid,omitempty" example:"wef12awefadf1221edcf"` + // Name is human-readable string to represent the object + Name string `json:"name" example:"sqldb01"` + // CspResourceName is name assigned to the CSP resource. This name is internally used to handle the resource. + CspResourceName string `json:"cspResourceName,omitempty" example:"we12fawefadf1221edcf"` + // CspResourceId is resource identifier managed by CSP + CspResourceId string `json:"cspResourceId,omitempty" example:"csp-06eb41e14121c550a"` + Status string `json:"status"` + Description string `json:"description"` + Details interface{} `json:"details"` +} diff --git a/src/core/model/sqlDb.go b/src/core/model/sqlDb.go index fc1679d9..dde52173 100644 --- a/src/core/model/sqlDb.go +++ b/src/core/model/sqlDb.go @@ -14,36 +14,36 @@ limitations under the License. // Package mci is to handle REST API for mci package model -type RestPostSqlDbRequest struct { - Name string `json:"name" validate:"required" example:"sqldb01"` - ConnectionName string `json:"connectionName" validate:"required" example:"aws-ap-northeast-2"` - CSP string `json:"csp" validate:"required" example:"aws"` - Region string `json:"region" validate:"required" example:"ap-northeast-2"` - DBInstanceSpec string `json:"dbInstanceSpec,omitempty" validate:"required" example:"db.t3.micro"` - DBEnginePort int `json:"dbEnginePort,omitempty" validate:"required" example:"3306"` - DBEngineVersion string `json:"dbEngineVersion,omitempty" validate:"required" example:"8.0.39"` - DBAdminUsername string `json:"dbAdminUsername" validate:"required" example:"mydbadmin"` - DBAdminPassword string `json:"dbAdminPassword" validate:"required" example:"Password1234!"` - RequiredCSPResource RequiredCSPResource `json:"requiredCSPResource,omitempty"` +type RestPostSqlDBRequest struct { + Name string `json:"name" validate:"required" example:"sqldb01"` + ConnectionName string `json:"connectionName" validate:"required" example:"aws-ap-northeast-2"` + CSP string `json:"csp" validate:"required" example:"aws"` + Region string `json:"region" validate:"required" example:"ap-northeast-2"` + DBInstanceSpec string `json:"dbInstanceSpec,omitempty" validate:"required" example:"db.t3.micro"` + DBEnginePort int `json:"dbEnginePort,omitempty" validate:"required" example:"3306"` + DBEngineVersion string `json:"dbEngineVersion,omitempty" validate:"required" example:"8.0.39"` + DBAdminUsername string `json:"dbAdminUsername" validate:"required" example:"mydbadmin"` + DBAdminPassword string `json:"dbAdminPassword" validate:"required" example:"Password1234!"` + RequiredCSPResource RequiredCSPResourceForSqlDB `json:"requiredCSPResource,omitempty"` } -type RequiredCSPResource struct { - AWS RequiredAWSResource `json:"aws,omitempty"` - Azure RequiredAzureResource `json:"azure,omitempty"` - NCP RequiredNCPResource `json:"ncp,omitempty"` +type RequiredCSPResourceForSqlDB struct { + AWS RequiredAWSResourceForSqlDB `json:"aws,omitempty"` + Azure RequiredAzureResourceForSqlDB `json:"azure,omitempty"` + NCP RequiredNCPResourceForSqlDB `json:"ncp,omitempty"` } -type RequiredAWSResource struct { +type RequiredAWSResourceForSqlDB struct { VNetID string `json:"vNetID,omitempty" example:"vpc-xxxxx"` Subnet1ID string `json:"subnet1ID,omitempty" example:"subnet-xxxx"` Subnet2ID string `json:"subnet2ID,omitempty" example:"subnet-xxxx in different AZ"` } -type RequiredAzureResource struct { +type RequiredAzureResourceForSqlDB struct { ResourceGroup string `json:"resourceGroup,omitempty" example:"koreacentral"` } -type RequiredNCPResource struct { +type RequiredNCPResourceForSqlDB struct { SubnetID string `json:"subnetID,omitempty" example:"123456"` } diff --git a/src/core/resource/common.go b/src/core/resource/common.go index a0b72a5c..531ead86 100644 --- a/src/core/resource/common.go +++ b/src/core/resource/common.go @@ -1133,6 +1133,7 @@ func CheckResource(nsId string, resourceType string, resourceId string) (bool, e resourceType == model.StrVNet || resourceType == model.StrVPN || resourceType == model.StrSqlDB || + resourceType == model.StrObjectStorage || resourceType == model.StrSecurityGroup || resourceType == model.StrDataDisk { //resourceType == "subnet" || diff --git a/src/core/resource/objectStorage.go b/src/core/resource/objectStorage.go new file mode 100644 index 00000000..bc47eb80 --- /dev/null +++ b/src/core/resource/objectStorage.go @@ -0,0 +1,1010 @@ +/* +Copyright 2019 The Cloud-Barista Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package resource is to manage multi-cloud infra resource +package resource + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/cloud-barista/cb-tumblebug/src/core/common" + "github.com/cloud-barista/cb-tumblebug/src/core/common/label" + "github.com/cloud-barista/cb-tumblebug/src/core/model" + "github.com/cloud-barista/cb-tumblebug/src/kvstore/kvstore" + terrariumModel "github.com/cloud-barista/mc-terrarium/pkg/api/rest/model" + "github.com/go-resty/resty/v2" + "github.com/rs/zerolog/log" +) + +// ObjectStorageStatus represents the status of a network resource. +type ObjectStorageStatus string + +const ( + + // CRUD operations + ObjectStorageOnConfiguring ObjectStorageStatus = "Configuring" // Resources are being configured. + // ObjectStorageOnReading ObjectStorageStatus = "Reading" // The network information is being read. + // ObjectStorageOnUpdating ObjectStorageStatus = "Updating" // The network is being updated. + ObjectStorageOnDeleting ObjectStorageStatus = "Deleting" // The network is being deleted. + // // ObjectStorageOnRefinining ObjectStorageStatus = "Refining" // The network is being refined. + + // // Register/deregister operations + // ObjectStorageOnRegistering ObjectStorageStatus = "Registering" // The network is being registered. + // ObjectStorageOnDeregistering ObjectStorageStatus = "Dergistering" // The network is being registered. + + // NetworkAvailable status + ObjectStorageAvailable ObjectStorageStatus = "Available" // The network is fully created and ready for use. + + // // In Use status + // ObjectStorageInUse ObjectStorageStatus = "InUse" // The network is currently in use. + + // // Unknwon status + // ObjectStorageUnknown ObjectStorageStatus = "Unknown" // The network status is unknown. + + // // ObjectStorageError Handling + // ObjectStorageError ObjectStorageStatus = "Error" // An error occurred during a CRUD operation. + // ObjectStorageErrorOnConfiguring ObjectStorageStatus = "ErrorOnConfiguring" // An error occurred during the configuring operation. + // ObjectStorageErrorOnReading ObjectStorageStatus = "ErrorOnReading" // An error occurred during the reading operation. + // ObjectStorageErrorOnUpdating ObjectStorageStatus = "ErrorOnUpdating" // An error occurred during the updating operation. + // ObjectStorageErrorOnDeleting ObjectStorageStatus = "ErrorOnDeleting" // An error occurred during the deleting operation. + // ObjectStorageErrorOnRegistering ObjectStorageStatus = "ErrorOnRegistering" // An error occurred during the registering operation. +) + +type ObjectStorageAction string + +var validCspForObjectStorage = map[string]bool{ + "aws": true, + "azure": true, + "gcp": true, + "ncp": true, + // "alibaba": true, + // "nhn": true, + // "kt": true, + + // Add more CSPs here +} + +func IsValidCspForObjectStorage(csp string) (bool, error) { + if !validCspForObjectStorage[csp] { + return false, fmt.Errorf("currently not supported CSP, %s", csp) + } + return true, nil +} + +// func whichCspForObjectStorage(csp1, csp2 string) string { +// return csp1 + "," + csp2 +// } + +// CreateObjectStorage creates a SQL database via Terrarium +func CreateObjectStorage(nsId string, objectStorageReq *model.RestPostObjectStorageRequest, retry string) (model.ObjectStorageInfo, error) { + + // Object Storage objects + var emptyRet model.ObjectStorageInfo + var objectStorageInfo model.ObjectStorageInfo + var err error = nil + var retried bool = (retry == "retry") + + /* + * Validate the input parameters + */ + + err = common.CheckString(nsId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = common.CheckString(objectStorageReq.Name) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + ok, err := IsValidCspForObjectStorage(objectStorageReq.CSP) + if !ok { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Check the CSPs of the sites + switch objectStorageReq.CSP { + case "aws": + // TODO: Check if the subnets are in the different AZs + // + + case "azure": + // Check the required CSP resources + if objectStorageReq.RequiredCSPResource.Azure.ResourceGroup == "" { + err = fmt.Errorf("required Azure resource group is empty") + log.Error().Err(err).Msg("") + return emptyRet, err + } + } + + // Set the resource type + resourceType := model.StrObjectStorage + + // Set the Object Storage object in advance + uid := common.GenUid() + objectStorageInfo.ResourceType = resourceType + objectStorageInfo.Name = objectStorageReq.Name + objectStorageInfo.Id = objectStorageReq.Name + objectStorageInfo.Uid = uid + objectStorageInfo.Description = "Object Storage at " + objectStorageReq.Region + " in " + objectStorageReq.CSP + objectStorageInfo.ConnectionName = objectStorageReq.ConnectionName + objectStorageInfo.ConnectionConfig, err = common.GetConnConfig(objectStorageInfo.ConnectionName) + if err != nil { + err = fmt.Errorf("Cannot retrieve ConnectionConfig" + err.Error()) + log.Error().Err(err).Msg("") + } + + // Set a objectStorageKey for the Object Storage object + objectStorageKey := common.GenResourceKey(nsId, resourceType, objectStorageInfo.Id) + // Check if the Object Storage resource already exists or not + exists, err := CheckResource(nsId, resourceType, objectStorageInfo.Id) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the resource type, %s (%s) exists or not", resourceType, objectStorageInfo.Id) + return emptyRet, err + } + // For retry, read the stored Object Storage info if exists + if exists { + if !retried { + err := fmt.Errorf("already exists, Object Storage: %s", objectStorageInfo.Id) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored Object Storage info + objectStorageKv, err := kvstore.GetKv(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(objectStorageKv.Value), &objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + objectStorageInfo.Name = objectStorageReq.Name + objectStorageInfo.Id = objectStorageReq.Name + objectStorageInfo.Description = "Object Storage at " + objectStorageReq.Region + " in " + objectStorageReq.CSP + objectStorageInfo.ConnectionName = objectStorageReq.ConnectionName + objectStorageInfo.ConnectionConfig, err = common.GetConnConfig(objectStorageInfo.ConnectionName) + if err != nil { + err = fmt.Errorf("Cannot retrieve ConnectionConfig" + err.Error()) + log.Error().Err(err).Msg("") + } + } + + // [Set and store status] + objectStorageInfo.Status = string(ObjectStorageOnConfiguring) + val, err := json.Marshal(objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(objectStorageKey, string(val)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("Object Storage Info(initial): %+v", objectStorageInfo) + + /* + * [Via Terrarium] Create a Object Storage + */ + + // Initialize resty client with basic auth + client := resty.New() + apiUser := os.Getenv("TB_API_USERNAME") + apiPass := os.Getenv("TB_API_PASSWORD") + client.SetBasicAuth(apiUser, apiPass) + + // Set Terrarium endpoint + epTerrarium := model.TerrariumRestUrl + + // Set a terrarium ID + trId := objectStorageInfo.Uid + + if !retried { + // Issue a terrarium + method := "POST" + url := fmt.Sprintf("%s/tr", epTerrarium) + reqTr := new(terrariumModel.TerrariumInfo) + reqTr.Id = trId + reqTr.Description = "Object Storage at " + objectStorageReq.Region + " in " + objectStorageReq.CSP + + resTrInfo := new(terrariumModel.TerrariumInfo) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(*reqTr), + reqTr, + resTrInfo, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resTrInfo.Id: %s", resTrInfo.Id) + log.Trace().Msgf("resTrInfo: %+v", resTrInfo) + + // init env + method = "POST" + url = fmt.Sprintf("%s/tr/%s/object-storage/env", epTerrarium, trId) + queryParams := "provider=" + objectStorageReq.CSP + url += "?" + queryParams + + requestBody := common.NoBody + resTerrariumEnv := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resTerrariumEnv, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resInit: %+v", resTerrariumEnv.Message) + log.Trace().Msgf("resInit: %+v", resTerrariumEnv.Detail) + } + + /* + * [Via Terrarium] Generate the infracode for the Object Storage of each CSP + */ + switch objectStorageReq.CSP { + case "aws": + // generate infracode + method := "POST" + url := fmt.Sprintf("%s/tr/%s/object-storage/infracode", epTerrarium, trId) + reqInfracode := new(terrariumModel.CreateInfracodeOfObjectStorageRequest) + reqInfracode.TfVars.TerrariumID = trId + reqInfracode.TfVars.CSPRegion = objectStorageReq.Region + // reqInfracode.TfVars.CSPResourceGroup + + resInfracode := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(*reqInfracode), + reqInfracode, + resInfracode, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + log.Debug().Msgf("resInfracode: %+v", resInfracode.Message) + log.Trace().Msgf("resInfracode: %+v", resInfracode.Detail) + + case "azure": + // generate infracode + method := "POST" + url := fmt.Sprintf("%s/tr/%s/object-storage/infracode", epTerrarium, trId) + reqInfracode := new(terrariumModel.CreateInfracodeOfObjectStorageRequest) + reqInfracode.TfVars.TerrariumID = trId + reqInfracode.TfVars.CSPRegion = objectStorageReq.Region + reqInfracode.TfVars.CSPResourceGroup = objectStorageReq.RequiredCSPResource.Azure.ResourceGroup + + resInfracode := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(*reqInfracode), + reqInfracode, + resInfracode, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + log.Debug().Msgf("resInfracode: %+v", resInfracode.Message) + log.Trace().Msgf("resInfracode: %+v", resInfracode.Detail) + + // case "gcp": + // // generate infracode + // method := "POST" + // url := fmt.Sprintf("%s/tr/%s/object-storage/infracode", epTerrarium, trId) + // reqInfracode := new(terrariumModel.CreateInfracodeOfObjectStorageRequest) + // reqInfracode.TfVars.TerrariumID = trId + // reqInfracode.TfVars.CSPRegion = objectStorageReq.Region + + // resInfracode := new(model.Response) + + // err = common.ExecuteHttpRequest( + // client, + // method, + // url, + // nil, + // common.SetUseBody(*reqInfracode), + // reqInfracode, + // resInfracode, + // common.VeryShortDuration, + // ) + + // if err != nil { + // log.Err(err).Msg("") + // return emptyRet, err + // } + // log.Debug().Msgf("resInfracode: %+v", resInfracode.Message) + // log.Trace().Msgf("resInfracode: %+v", resInfracode.Detail) + + // case "ncp": + // // generate infracode + // method := "POST" + // url := fmt.Sprintf("%s/tr/%s/object-storage/infracode", epTerrarium, trId) + // reqInfracode := new(terrariumModel.CreateInfracodeOfObjectStorageRequest) + // reqInfracode.TfVars.TerrariumID = trId + // reqInfracode.TfVars.CSPRegion = objectStorageReq.Region + + // resInfracode := new(model.Response) + + // err = common.ExecuteHttpRequest( + // client, + // method, + // url, + // nil, + // common.SetUseBody(*reqInfracode), + // reqInfracode, + // resInfracode, + // common.VeryShortDuration, + // ) + + // if err != nil { + // log.Err(err).Msg("") + // return emptyRet, err + // } + // log.Debug().Msgf("resInfracode: %+v", resInfracode.Message) + // log.Trace().Msgf("resInfracode: %+v", resInfracode.Detail) + + default: + log.Warn().Msgf("not valid CSP: %s", objectStorageReq.CSP) + } + + /* + * [Via Terrarium] Check the infracode + */ + + // check the infracode (by `tofu plan`) + method := "POST" + url := fmt.Sprintf("%s/tr/%s/object-storage/plan", epTerrarium, trId) + requestBody := common.NoBody + resPlan := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resPlan, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + log.Debug().Msgf("resPlan: %+v", resPlan.Message) + log.Trace().Msgf("resPlan: %+v", resPlan.Detail) + + // apply + // wait until the task is completed + // or response immediately with requestId as it is a time-consuming task + // and provide seperate api to check the status + method = "POST" + url = fmt.Sprintf("%s/tr/%s/object-storage", epTerrarium, trId) + requestBody = common.NoBody + resApply := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resApply, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + log.Debug().Msgf("resApply: %+v", resApply.Message) + log.Trace().Msgf("resApply: %+v", resApply.Detail) + + // Set the Object Storage info + var trObjectStorageInfo terrariumModel.OutputObjectStorageInfo + jsonData, err := json.Marshal(resApply.Object) + if err != nil { + log.Error().Err(err).Msg("") + } + err = json.Unmarshal(jsonData, &trObjectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + } + + objectStorageInfo.CspResourceId = "" + objectStorageInfo.CspResourceName = trObjectStorageInfo.ObjectStorageDetail.StorageName + objectStorageInfo.Details = trObjectStorageInfo.ObjectStorageDetail + + /* + * Set opeartion status and store objectStorageInfo + */ + + objectStorageInfo.Status = string(ObjectStorageAvailable) + + log.Debug().Msgf("Object Storage Info(final): %+v", objectStorageInfo) + + value, err := json.Marshal(objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(objectStorageKey, string(value)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Check if the Object Storage info is stored + objectStorageKv, err := kvstore.GetKv(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + if objectStorageKv == (kvstore.KeyValue{}) { + err := fmt.Errorf("does not exist, Object Storage: %s", objectStorageInfo.Id) + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(objectStorageKv.Value), &objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Store label info using CreateOrUpdateLabel + labels := map[string]string{ + model.LabelManager: model.StrManager, + model.LabelNamespace: nsId, + model.LabelLabelType: model.StrObjectStorage, + model.LabelId: objectStorageInfo.Id, + model.LabelName: objectStorageInfo.Name, + model.LabelUid: objectStorageInfo.Uid, + model.LabelCspResourceId: objectStorageInfo.CspResourceId, + model.LabelCspResourceName: objectStorageInfo.CspResourceName, + model.LabelStatus: objectStorageInfo.Status, + model.LabelDescription: objectStorageInfo.Description, + } + err = label.CreateOrUpdateLabel(model.StrObjectStorage, objectStorageInfo.Uid, objectStorageKey, labels) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + return objectStorageInfo, nil +} + +// GetObjectStorage returns a Object Storage via Terrarium +func GetObjectStorage(nsId string, objectStorageId string, detail string) (model.ObjectStorageInfo, error) { + + var emptyRet model.ObjectStorageInfo + var objectStorageInfo model.ObjectStorageInfo + var err error = nil + /* + * Validate the input parameters + */ + + err = common.CheckString(nsId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = common.CheckString(objectStorageId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + if detail != "refined" && detail != "raw" && detail != "" { + err = fmt.Errorf("not valid detail: %s", detail) + log.Error().Err(err).Msg("") + return emptyRet, err + } + if detail == "" { + log.Warn().Msg("detail is empty, set to refined") + detail = "refined" + } + + // Set the resource type + resourceType := model.StrObjectStorage + + // Set a objectStorageKey for the Object Storage object + objectStorageKey := common.GenResourceKey(nsId, resourceType, objectStorageId) + // Check if the Object Storage resource already exists or not + exists, err := CheckResource(nsId, resourceType, objectStorageId) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the Object Storage(%s) exists or not", objectStorageId) + return emptyRet, err + } + if !exists { + err := fmt.Errorf("does not exist, Object Storage: %s", objectStorageId) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored Object Storage info + objectStorageKv, err := kvstore.GetKv(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(objectStorageKv.Value), &objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Initialize resty client with basic auth + client := resty.New() + apiUser := os.Getenv("TB_API_USERNAME") + apiPass := os.Getenv("TB_API_PASSWORD") + client.SetBasicAuth(apiUser, apiPass) + + trId := objectStorageInfo.Uid + + // set endpoint + epTerrarium := model.TerrariumRestUrl + + // Get the terrarium info + method := "GET" + url := fmt.Sprintf("%s/tr/%s", epTerrarium, trId) + requestBody := common.NoBody + resTrInfo := new(terrariumModel.TerrariumInfo) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resTrInfo, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resTrInfo.Id: %s", resTrInfo.Id) + log.Trace().Msgf("resTrInfo: %+v", resTrInfo) + + // e.g. "object-storage" + enrichments := resTrInfo.Enrichments + + // Get resource info + method = "GET" + url = fmt.Sprintf("%s/tr/%s/%s?detail=%s", epTerrarium, trId, enrichments, detail) + requestBody = common.NoBody + resResourceInfo := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resResourceInfo, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + var trObjectStorageInfo terrariumModel.OutputObjectStorageInfo + jsonData, err := json.Marshal(resResourceInfo.Object) + if err != nil { + log.Error().Err(err).Msg("") + } + err = json.Unmarshal(jsonData, &trObjectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + } + + objectStorageInfo.CspResourceId = "" + objectStorageInfo.CspResourceName = trObjectStorageInfo.ObjectStorageDetail.StorageName + objectStorageInfo.Details = trObjectStorageInfo.ObjectStorageDetail + + log.Debug().Msgf("Object Storage Info(final): %+v", objectStorageInfo) + + value, err := json.Marshal(objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(objectStorageKey, string(value)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Check if the Object Storage info is stored + objectStorageKv, err = kvstore.GetKv(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + if objectStorageKv == (kvstore.KeyValue{}) { + err := fmt.Errorf("does not exist, Object Storage: %s", objectStorageInfo.Id) + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(objectStorageKv.Value), &objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + return objectStorageInfo, nil +} + +// DeleteObjectStorage deletes a SQL database via Terrarium +func DeleteObjectStorage(nsId string, objectStorageId string) (model.SimpleMsg, error) { + + // VPN objects + var emptyRet model.SimpleMsg + var objectStorageInfo model.ObjectStorageInfo + var err error = nil + + /* + * Validate the input parameters + */ + + err = common.CheckString(nsId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = common.CheckString(objectStorageId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Set the resource type + resourceType := model.StrObjectStorage + + // Set a objectStorageKey for the Object Storage object + objectStorageKey := common.GenResourceKey(nsId, resourceType, objectStorageId) + // Check if the Object Storage resource already exists or not + exists, err := CheckResource(nsId, resourceType, objectStorageId) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the Object Storage (%s) exists or not", objectStorageId) + return emptyRet, err + } + if !exists { + err := fmt.Errorf("does not exist, Object Storage: %s", objectStorageId) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored Object Storage info + objectStorageKv, err := kvstore.GetKv(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(objectStorageKv.Value), &objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // [Set and store status] + objectStorageInfo.Status = string(ObjectStorageOnDeleting) + val, err := json.Marshal(objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(objectStorageKey, string(val)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Initialize resty client with basic auth + client := resty.New() + apiUser := os.Getenv("TB_API_USERNAME") + apiPass := os.Getenv("TB_API_PASSWORD") + client.SetBasicAuth(apiUser, apiPass) + + trId := objectStorageInfo.Uid + + // set endpoint + epTerrarium := model.TerrariumRestUrl + + // Get the terrarium info + method := "GET" + url := fmt.Sprintf("%s/tr/%s", epTerrarium, trId) + requestBody := common.NoBody + resTrInfo := new(terrariumModel.TerrariumInfo) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resTrInfo, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resTrInfo.Id: %s", resTrInfo.Id) + log.Trace().Msgf("resTrInfo: %+v", resTrInfo) + enrichments := resTrInfo.Enrichments + + // delete enrichments + method = "DELETE" + url = fmt.Sprintf("%s/tr/%s/%s", epTerrarium, trId, enrichments) + requestBody = common.NoBody + resDeleteEnrichments := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resDeleteEnrichments, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resDeleteEnrichments: %+v", resDeleteEnrichments.Message) + log.Trace().Msgf("resDeleteEnrichments: %+v", resDeleteEnrichments.Detail) + + // delete env + method = "DELETE" + url = fmt.Sprintf("%s/tr/%s/%s/env", epTerrarium, trId, enrichments) + requestBody = common.NoBody + resDeleteEnv := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resDeleteEnv, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resDeleteEnv: %+v", resDeleteEnv.Message) + log.Trace().Msgf("resDeleteEnv: %+v", resDeleteEnv.Detail) + + // delete terrarium + method = "DELETE" + url = fmt.Sprintf("%s/tr/%s", epTerrarium, trId) + requestBody = common.NoBody + resDeleteTr := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resDeleteTr, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resDeleteTr: %+v", resDeleteTr.Message) + log.Trace().Msgf("resDeleteTr: %+v", resDeleteTr.Detail) + + // [Set and store status] + err = kvstore.Delete(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Remove label info using DeleteLabelObject + err = label.DeleteLabelObject(model.StrObjectStorage, objectStorageInfo.Uid) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + res := model.SimpleMsg{ + Message: resDeleteTr.Message, + } + + return res, nil +} + +// GetRequestStatusOfObjectStorage checks the status of a specific request +func GetRequestStatusOfObjectStorage(nsId string, objectStorageId string, reqId string) (model.Response, error) { + + var emptyRet model.Response + var objectStorageInfo model.ObjectStorageInfo + var err error = nil + + /* + * Validate the input parameters + */ + + err = common.CheckString(nsId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = common.CheckString(objectStorageId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Set the resource type + resourceType := model.StrObjectStorage + + // Set a objectStorageKey for the Object Storage object + objectStorageKey := common.GenResourceKey(nsId, resourceType, objectStorageId) + // Check if the Object Storage resource already exists or not + exists, err := CheckResource(nsId, resourceType, objectStorageId) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the Object Storage(%s) exists or not", objectStorageId) + return emptyRet, err + } + if !exists { + err := fmt.Errorf("does not exist, Object Storage: %s", objectStorageId) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored Object Storage info + objectStorageKv, err := kvstore.GetKv(objectStorageKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(objectStorageKv.Value), &objectStorageInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Initialize resty client with basic auth + client := resty.New() + apiUser := os.Getenv("TB_API_USERNAME") + apiPass := os.Getenv("TB_API_PASSWORD") + client.SetBasicAuth(apiUser, apiPass) + + trId := objectStorageInfo.Uid + + // set endpoint + epTerrarium := model.TerrariumRestUrl + + // Get the terrarium info + method := "GET" + url := fmt.Sprintf("%s/tr/%s", epTerrarium, trId) + requestBody := common.NoBody + resTrInfo := new(terrariumModel.TerrariumInfo) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(requestBody), + &requestBody, + resTrInfo, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("resTrInfo.Id: %s", resTrInfo.Id) + log.Trace().Msgf("resTrInfo: %+v", resTrInfo) + enrichments := resTrInfo.Enrichments + + // Get resource info + method = "GET" + url = fmt.Sprintf("%s/tr/%s/%s/request/%s", epTerrarium, trId, enrichments, reqId) + reqReqStatus := common.NoBody + resReqStatus := new(model.Response) + + err = common.ExecuteHttpRequest( + client, + method, + url, + nil, + common.SetUseBody(reqReqStatus), + &reqReqStatus, + resReqStatus, + common.VeryShortDuration, + ) + + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + log.Debug().Msgf("resReqStatus: %+v", resReqStatus.Detail) + + return *resReqStatus, nil +} diff --git a/src/core/resource/sqlDb.go b/src/core/resource/sqlDb.go index db4cb000..8911cd10 100644 --- a/src/core/resource/sqlDb.go +++ b/src/core/resource/sqlDb.go @@ -87,8 +87,8 @@ func IsValidCspForSqlDB(csp string) (bool, error) { // return csp1 + "," + csp2 // } -// CreateSqlDb creates a SQL database via Terrarium -func CreateSqlDb(nsId string, sqlDbReq *model.RestPostSqlDbRequest, retry string) (model.SqlDBInfo, error) { +// CreateSqlDB creates a SQL database via Terrarium +func CreateSqlDB(nsId string, sqlDbReq *model.RestPostSqlDBRequest, retry string) (model.SqlDBInfo, error) { // SQL DB objects var emptyRet model.SqlDBInfo @@ -565,8 +565,8 @@ func CreateSqlDb(nsId string, sqlDbReq *model.RestPostSqlDbRequest, retry string return sqlDBInfo, nil } -// GetSqlDb returns a SQL DB via Terrarium -func GetSqlDb(nsId string, sqlDbId string, detail string) (model.SqlDBInfo, error) { +// GetSqlDB returns a SQL DB via Terrarium +func GetSqlDB(nsId string, sqlDbId string, detail string) (model.SqlDBInfo, error) { var emptyRet model.SqlDBInfo var sqlDBInfo model.SqlDBInfo @@ -686,43 +686,19 @@ func GetSqlDb(nsId string, sqlDbId string, detail string) (model.SqlDBInfo, erro return emptyRet, err } - // switch enrichments { - // case "vpn/gcp-aws": - var trVpnInfo terrariumModel.OutputSQLDBInfo + var trSqlDBInfo terrariumModel.OutputSQLDBInfo jsonData, err := json.Marshal(resResourceInfo.Object) if err != nil { log.Error().Err(err).Msg("") } - err = json.Unmarshal(jsonData, &trVpnInfo) + err = json.Unmarshal(jsonData, &trSqlDBInfo) if err != nil { log.Error().Err(err).Msg("") } - sqlDBInfo.CspResourceId = trVpnInfo.SQLDBDetail.InstanceResourceID - sqlDBInfo.CspResourceName = trVpnInfo.SQLDBDetail.InstanceName - sqlDBInfo.Details = trVpnInfo.SQLDBDetail - - // case "vpn/gcp-azure": - // var trVpnInfo terrariumModel.OutputGcpAzureVpnInfo - // jsonData, err := json.Marshal(resResourceInfo.Object) - // if err != nil { - // log.Error().Err(err).Msg("") - // } - // err = json.Unmarshal(jsonData, &trVpnInfo) - // if err != nil { - // log.Error().Err(err).Msg("") - // } - - // sqlDBInfo.VPNGatewayInfo[0].CspResourceId = trVpnInfo.Azure.VirtualNetworkGateway.ID - // sqlDBInfo.VPNGatewayInfo[0].CspResourceName = trVpnInfo.Azure.VirtualNetworkGateway.Name - // sqlDBInfo.VPNGatewayInfo[0].Details = trVpnInfo.Azure - // sqlDBInfo.VPNGatewayInfo[1].CspResourceId = trVpnInfo.GCP.HaVpnGateway.ID - // sqlDBInfo.VPNGatewayInfo[1].CspResourceName = trVpnInfo.GCP.HaVpnGateway.Name - // sqlDBInfo.VPNGatewayInfo[1].Details = trVpnInfo.GCP - // default: - // log.Warn().Msgf("not valid enrichments: %s", enrichments) - // return emptyRet, fmt.Errorf("not valid enrichments: %s", enrichments) - // } + sqlDBInfo.CspResourceId = trSqlDBInfo.SQLDBDetail.InstanceResourceID + sqlDBInfo.CspResourceName = trSqlDBInfo.SQLDBDetail.InstanceName + sqlDBInfo.Details = trSqlDBInfo.SQLDBDetail log.Debug().Msgf("SQL DB Info(final): %+v", sqlDBInfo) @@ -757,8 +733,8 @@ func GetSqlDb(nsId string, sqlDbId string, detail string) (model.SqlDBInfo, erro return sqlDBInfo, nil } -// DeleteSqlDb deletes a SQL database via Terrarium -func DeleteSqlDb(nsId string, sqlDbId string) (model.SimpleMsg, error) { +// DeleteSqlDB deletes a SQL database via Terrarium +func DeleteSqlDB(nsId string, sqlDbId string) (model.SimpleMsg, error) { // VPN objects var emptyRet model.SimpleMsg @@ -956,8 +932,8 @@ func DeleteSqlDb(nsId string, sqlDbId string) (model.SimpleMsg, error) { return res, nil } -// GetRequestStatusOfSqlDb checks the status of a specific request -func GetRequestStatusOfSqlDb(nsId string, sqlDbId string, reqId string) (model.Response, error) { +// GetRequestStatusOfSqlDB checks the status of a specific request +func GetRequestStatusOfSqlDB(nsId string, sqlDbId string, reqId string) (model.Response, error) { var emptyRet model.Response var sqlDBInfo model.SqlDBInfo