diff --git a/go.mod b/go.mod index 6a802b86..a1a425c8 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.10 + github.com/cloud-barista/mc-terrarium v0.0.12 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.work.sum b/go.work.sum index 21c059f5..01c4b858 100644 --- a/go.work.sum +++ b/go.work.sum @@ -384,7 +384,10 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cloud-barista/cb-log v0.6.4 h1:HblyslcRLeD2tib4UcZszJ8ZNfB4W0CRVvbKlf9uY78= github.com/cloud-barista/cb-log v0.6.4/go.mod h1:nGgfTFMPwl1MpCO3FBjexUkNdOYA0BNJoyM9Pd0lMms= github.com/cloud-barista/cb-tumblebug/src v0.0.0-20230724172618-8f225d0127e8/go.mod h1:XQuz7L64MNUu04FmG5gB0z41VcrfaLuQP80EGyQTDgo= -github.com/cloud-barista/mc-terrarium v0.0.10/go.mod h1:iQxZNRa04d7mHA0h5dEPfF7ch1SBUS/ZFGUynKsKJ6I= +github.com/cloud-barista/mc-terrarium v0.0.11 h1:qfwie72bzJzYSca24BugO23Iv9aq4gIqiik1BTWUl7g= +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/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 a3a6bb33..d2b64ed1 100644 --- a/src/api/rest/docs/docs.go +++ b/src/api/rest/docs/docs.go @@ -993,7 +993,8 @@ const docTemplate = `{ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -1056,7 +1057,8 @@ const docTemplate = `{ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -1130,7 +1132,8 @@ const docTemplate = `{ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -8161,6 +8164,329 @@ const docTemplate = `{ } } }, + "/ns/{nsId}/resources/sqlDb": { + "get": { + "description": "Get all SQL Databases", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Get all SQL Databases", + "operationId": "GetAllSqlDb", + "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 SQL Databases\n\n- Supported CSPs: AWS (Upcoming CSPs: Azure, GCP, NCP)\n\n- Please check the values ​​required for each CSP through the ` + "`" + `requiredCSPResource` + "`" + ` property.\n", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Create a SQL Databases", + "operationId": "PostSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "description": "Request body to create a SQL database", + "name": "sqlDbReq", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.RestPostSqlDbRequest" + } + }, + { + "enum": [ + "retry" + ], + "type": "string", + "description": "Action", + "name": "action", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.SqlDBInfo" + } + }, + "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/sqlDb/{sqlDbId}": { + "get": { + "description": "Get resource info of a SQL datatbase", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Get resource info of a SQL datatbase", + "operationId": "GetSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "sqldb01", + "description": "SQL DB ID", + "name": "sqlDbId", + "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.SqlDBInfo" + } + }, + "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 SQL datatbase", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Delete a SQL datatbase", + "operationId": "DeleteSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "sqldb01", + "description": "SQL DB ID", + "name": "sqlDbId", + "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/sqlDb/{sqlDbId}/request/{requestId}": { + "get": { + "description": "Check the status of a specific request by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Check the status of a specific request by its ID", + "operationId": "GetRequestStatusOfSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "sqldb01", + "description": "SQL DB ID", + "name": "sqlDbId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Request ID", + "name": "requestId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "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/sshKey": { "get": { "description": "List all SSH Keys or SSH Keys' ID", @@ -9963,7 +10289,8 @@ const docTemplate = `{ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -11999,6 +12326,55 @@ const docTemplate = `{ } } }, + "model.RequiredAWSResource": { + "type": "object", + "properties": { + "subnet1ID": { + "type": "string", + "example": "subnet-xxxx" + }, + "subnet2ID": { + "type": "string", + "example": "subnet-xxxx in different AZ" + }, + "vNetID": { + "type": "string", + "example": "vpc-xxxxx" + } + } + }, + "model.RequiredAzureResource": { + "type": "object", + "properties": { + "resourceGroup": { + "type": "string", + "example": "rg-xxxx" + } + } + }, + "model.RequiredCSPResource": { + "type": "object", + "properties": { + "aws": { + "$ref": "#/definitions/model.RequiredAWSResource" + }, + "azure": { + "$ref": "#/definitions/model.RequiredAzureResource" + }, + "ncp": { + "$ref": "#/definitions/model.RequiredNCPResource" + } + } + }, + "model.RequiredNCPResource": { + "type": "object", + "properties": { + "subnetID": { + "type": "string", + "example": "subnet-xxxx" + } + } + }, "model.ResourceCountOverview": { "type": "object", "properties": { @@ -12146,6 +12522,61 @@ const docTemplate = `{ } } }, + "model.RestPostSqlDbRequest": { + "type": "object", + "required": [ + "connectionName", + "csp", + "dbAdminPassword", + "dbAdminUsername", + "dbEnginePort", + "dbEngineVersion", + "dbInstanceSpec", + "name", + "region" + ], + "properties": { + "connectionName": { + "type": "string", + "example": "aws-ap-northeast-2" + }, + "csp": { + "type": "string", + "example": "aws" + }, + "dbAdminPassword": { + "type": "string", + "example": "Password1234!" + }, + "dbAdminUsername": { + "type": "string", + "example": "mydbadmin" + }, + "dbEnginePort": { + "type": "integer", + "example": 3306 + }, + "dbEngineVersion": { + "type": "string", + "example": "8.0.39" + }, + "dbInstanceSpec": { + "type": "string", + "example": "db.t3.micro" + }, + "name": { + "type": "string", + "example": "sqldb01" + }, + "region": { + "type": "string", + "example": "ap-northeast-2" + }, + "requiredCSPResource": { + "$ref": "#/definitions/model.RequiredCSPResource" + } + } + }, "model.RestPostVpnRequest": { "type": "object", "required": [ @@ -12594,6 +13025,53 @@ const docTemplate = `{ } } }, + "model.SqlDBInfo": { + "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.SshCmdResult": { "type": "object", "properties": { diff --git a/src/api/rest/docs/swagger.json b/src/api/rest/docs/swagger.json index 56a6540c..b68aa303 100644 --- a/src/api/rest/docs/swagger.json +++ b/src/api/rest/docs/swagger.json @@ -986,7 +986,8 @@ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -1049,7 +1050,8 @@ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -1123,7 +1125,8 @@ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -8154,6 +8157,329 @@ } } }, + "/ns/{nsId}/resources/sqlDb": { + "get": { + "description": "Get all SQL Databases", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Get all SQL Databases", + "operationId": "GetAllSqlDb", + "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 SQL Databases\n\n- Supported CSPs: AWS (Upcoming CSPs: Azure, GCP, NCP)\n\n- Please check the values ​​required for each CSP through the `requiredCSPResource` property.\n", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Create a SQL Databases", + "operationId": "PostSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "description": "Request body to create a SQL database", + "name": "sqlDbReq", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.RestPostSqlDbRequest" + } + }, + { + "enum": [ + "retry" + ], + "type": "string", + "description": "Action", + "name": "action", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.SqlDBInfo" + } + }, + "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/sqlDb/{sqlDbId}": { + "get": { + "description": "Get resource info of a SQL datatbase", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Get resource info of a SQL datatbase", + "operationId": "GetSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "sqldb01", + "description": "SQL DB ID", + "name": "sqlDbId", + "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.SqlDBInfo" + } + }, + "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 SQL datatbase", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Delete a SQL datatbase", + "operationId": "DeleteSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "sqldb01", + "description": "SQL DB ID", + "name": "sqlDbId", + "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/sqlDb/{sqlDbId}/request/{requestId}": { + "get": { + "description": "Check the status of a specific request by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Infra Resource] SQL Database Management (under development)" + ], + "summary": "Check the status of a specific request by its ID", + "operationId": "GetRequestStatusOfSqlDb", + "parameters": [ + { + "type": "string", + "default": "default", + "description": "Namespace ID", + "name": "nsId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "sqldb01", + "description": "SQL DB ID", + "name": "sqlDbId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Request ID", + "name": "requestId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "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/sshKey": { "get": { "description": "List all SSH Keys or SSH Keys' ID", @@ -9956,7 +10282,8 @@ "vpn", "securityGroup", "sshKey", - "dataDisk" + "dataDisk", + "sqlDb" ], "type": "string", "description": "Label Type", @@ -11992,6 +12319,55 @@ } } }, + "model.RequiredAWSResource": { + "type": "object", + "properties": { + "subnet1ID": { + "type": "string", + "example": "subnet-xxxx" + }, + "subnet2ID": { + "type": "string", + "example": "subnet-xxxx in different AZ" + }, + "vNetID": { + "type": "string", + "example": "vpc-xxxxx" + } + } + }, + "model.RequiredAzureResource": { + "type": "object", + "properties": { + "resourceGroup": { + "type": "string", + "example": "rg-xxxx" + } + } + }, + "model.RequiredCSPResource": { + "type": "object", + "properties": { + "aws": { + "$ref": "#/definitions/model.RequiredAWSResource" + }, + "azure": { + "$ref": "#/definitions/model.RequiredAzureResource" + }, + "ncp": { + "$ref": "#/definitions/model.RequiredNCPResource" + } + } + }, + "model.RequiredNCPResource": { + "type": "object", + "properties": { + "subnetID": { + "type": "string", + "example": "subnet-xxxx" + } + } + }, "model.ResourceCountOverview": { "type": "object", "properties": { @@ -12139,6 +12515,61 @@ } } }, + "model.RestPostSqlDbRequest": { + "type": "object", + "required": [ + "connectionName", + "csp", + "dbAdminPassword", + "dbAdminUsername", + "dbEnginePort", + "dbEngineVersion", + "dbInstanceSpec", + "name", + "region" + ], + "properties": { + "connectionName": { + "type": "string", + "example": "aws-ap-northeast-2" + }, + "csp": { + "type": "string", + "example": "aws" + }, + "dbAdminPassword": { + "type": "string", + "example": "Password1234!" + }, + "dbAdminUsername": { + "type": "string", + "example": "mydbadmin" + }, + "dbEnginePort": { + "type": "integer", + "example": 3306 + }, + "dbEngineVersion": { + "type": "string", + "example": "8.0.39" + }, + "dbInstanceSpec": { + "type": "string", + "example": "db.t3.micro" + }, + "name": { + "type": "string", + "example": "sqldb01" + }, + "region": { + "type": "string", + "example": "ap-northeast-2" + }, + "requiredCSPResource": { + "$ref": "#/definitions/model.RequiredCSPResource" + } + } + }, "model.RestPostVpnRequest": { "type": "object", "required": [ @@ -12587,6 +13018,53 @@ } } }, + "model.SqlDBInfo": { + "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.SshCmdResult": { "type": "object", "properties": { diff --git a/src/api/rest/docs/swagger.yaml b/src/api/rest/docs/swagger.yaml index 682799bf..a4cb1fcc 100644 --- a/src/api/rest/docs/swagger.yaml +++ b/src/api/rest/docs/swagger.yaml @@ -744,6 +744,7 @@ paths: - securityGroup - sshKey - dataDisk + - sqlDb - name: uid in: path description: Resource uid @@ -794,6 +795,7 @@ paths: - securityGroup - sshKey - dataDisk + - sqlDb - name: uid in: path description: Resource uid @@ -853,6 +855,7 @@ paths: - securityGroup - sshKey - dataDisk + - sqlDb - name: uid in: path description: Resource uid @@ -6206,6 +6209,266 @@ paths: application/json: schema: $ref: '#/components/schemas/model.SimpleMsg' + /ns/{nsId}/resources/sqlDb: + get: + tags: + - "[Infra Resource] SQL Database Management (under development)" + summary: Get all SQL Databases + description: Get all SQL Databases + operationId: GetAllSqlDb + 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] SQL Database Management (under development)" + summary: Create a SQL Databases + description: | + Create a SQL Databases + + - Supported CSPs: AWS (Upcoming CSPs: Azure, GCP, NCP) + + - Please check the values ​​required for each CSP through the `requiredCSPResource` property. + operationId: PostSqlDb + 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 SQL database + content: + application/json: + schema: + $ref: '#/components/schemas/model.RestPostSqlDbRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/model.SqlDBInfo' + "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: sqlDbReq + /ns/{nsId}/resources/sqlDb/{sqlDbId}: + get: + tags: + - "[Infra Resource] SQL Database Management (under development)" + summary: Get resource info of a SQL datatbase + description: Get resource info of a SQL datatbase + operationId: GetSqlDb + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: sqlDbId + in: path + description: SQL DB ID + required: true + schema: + type: string + default: sqldb01 + - 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.SqlDBInfo' + "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] SQL Database Management (under development)" + summary: Delete a SQL datatbase + description: Delete a SQL datatbase + operationId: DeleteSqlDb + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: sqlDbId + in: path + description: SQL DB ID + required: true + schema: + type: string + default: sqldb01 + 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/sqlDb/{sqlDbId}/request/{requestId}: + get: + tags: + - "[Infra Resource] SQL Database Management (under development)" + summary: Check the status of a specific request by its ID + description: Check the status of a specific request by its ID + operationId: GetRequestStatusOfSqlDb + parameters: + - name: nsId + in: path + description: Namespace ID + required: true + schema: + type: string + default: default + - name: sqlDbId + in: path + description: SQL DB ID + required: true + schema: + type: string + default: sqldb01 + - name: requestId + in: path + description: Request ID + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/model.Response' + "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/sshKey: get: tags: @@ -7575,6 +7838,7 @@ paths: - securityGroup - sshKey - dataDisk + - sqlDb - name: labelSelector in: query description: "Label selector query. Example: env=production,tier=backend" @@ -8978,6 +9242,39 @@ components: type: integer vm: type: integer + model.RequiredAWSResource: + type: object + properties: + subnet1ID: + type: string + example: subnet-xxxx + subnet2ID: + type: string + example: subnet-xxxx in different AZ + vNetID: + type: string + example: vpc-xxxxx + model.RequiredAzureResource: + type: object + properties: + resourceGroup: + type: string + example: rg-xxxx + model.RequiredCSPResource: + type: object + properties: + aws: + $ref: '#/components/schemas/model.RequiredAWSResource' + azure: + $ref: '#/components/schemas/model.RequiredAzureResource' + ncp: + $ref: '#/components/schemas/model.RequiredNCPResource' + model.RequiredNCPResource: + type: object + properties: + subnetID: + type: string + example: subnet-xxxx model.ResourceCountOverview: type: object properties: @@ -9076,6 +9373,48 @@ components: success: type: boolean example: true + model.RestPostSqlDbRequest: + required: + - connectionName + - csp + - dbAdminPassword + - dbAdminUsername + - dbEnginePort + - dbEngineVersion + - dbInstanceSpec + - name + - region + type: object + properties: + connectionName: + type: string + example: aws-ap-northeast-2 + csp: + type: string + example: aws + dbAdminPassword: + type: string + example: Password1234! + dbAdminUsername: + type: string + example: mydbadmin + dbEnginePort: + type: integer + example: 3306 + dbEngineVersion: + type: string + example: 8.0.39 + dbInstanceSpec: + type: string + example: db.t3.micro + name: + type: string + example: sqldb01 + region: + type: string + example: ap-northeast-2 + requiredCSPResource: + $ref: '#/components/schemas/model.RequiredCSPResource' model.RestPostVpnRequest: required: - name @@ -9387,6 +9726,44 @@ components: description: GHz count: type: string + model.SqlDBInfo: + 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.SshCmdResult: type: object properties: diff --git a/src/api/rest/server/common/label/label.go b/src/api/rest/server/common/label/label.go index 93d60a40..347cd30b 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) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) // @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) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) // @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) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) // @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) +// @Param labelType path string true "Label Type" Enums(ns, mci, subGroup, vm, k8s, vNet, subnet, vpn, securityGroup, sshKey, dataDisk, sqlDb) // @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/sqlDb.go b/src/api/rest/server/resource/sqlDb.go new file mode 100644 index 00000000..90baf341 --- /dev/null +++ b/src/api/rest/server/resource/sqlDb.go @@ -0,0 +1,620 @@ +/* +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] SQL Database 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 +// } + +// RestGetAllSqlDb godoc +// @ID GetAllSqlDb +// @Summary Get all SQL Databases +// @Description Get all SQL Databases +// @Tags [Infra Resource] SQL Database 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/sqlDb [get] +func RestGetAllSqlDb(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{}) + +} + +// RestPostSqlDb godoc +// @ID PostSqlDb +// @Summary Create a SQL Databases +// @Description Create a SQL Databases +// @Description +// @Description - Supported CSPs: AWS (Upcoming CSPs: Azure, GCP, NCP) +// @Description +// @Description - Please check the values ​​required for each CSP through the `requiredCSPResource` property. +// @Description +// @Tags [Infra Resource] SQL Database Management (under development) +// @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 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 { + + 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 RestPostSqlDbRequest struct + 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()}) + } + + // Validate the CSP is supported + sqlDbReq.CSP = strings.ToLower(sqlDbReq.CSP) + ok, err := resource.IsValidCspForSqlDB(sqlDbReq.CSP) + if !ok { + log.Warn().Err(err).Msg("") + res := model.SimpleMsg{ + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + err = common.CheckString(sqlDbReq.Name) + if err != nil { + errMsg := fmt.Errorf("invalid sqlDbName (%s)", sqlDbReq.Name) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + var resp model.SqlDBInfo + resp, err = resource.CreateSqlDb(nsId, sqlDbReq, action) + if err != nil { + log.Err(err).Msg("") + return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + } + + return c.JSON(http.StatusOK, resp) + +} + +// RestGetSqlDb godoc +// @ID GetSqlDb +// @Summary Get resource info of a SQL datatbase +// @Description Get resource info of a SQL datatbase +// @Tags [Infra Resource] SQL Database Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param sqlDbId path string true "SQL DB ID" default(sqldb01) +// @Param detail query string false "Resource info by detail (refined, raw)" default(refined) +// @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/{sqlDbId} [get] +func RestGetSqlDb(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()}) + } + + sqlDbId := c.Param("sqlDbId") + err = common.CheckString(sqlDbId) + if err != nil { + errMsg := fmt.Errorf("invalid sqlDbId (%s)", sqlDbId) + 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.SqlDBInfo + // currently, only support detail=refined + detail := "refined" + resp, err = resource.GetSqlDb(nsId, sqlDbId, detail) + if err != nil { + log.Err(err).Msg("") + return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + } + + return c.JSON(http.StatusOK, resp) +} + +// RestDeleteSqlDb godoc +// @ID DeleteSqlDb +// @Summary Delete a SQL datatbase +// @Description Delete a SQL datatbase +// @Tags [Infra Resource] SQL Database Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param sqlDbId path string true "SQL DB ID" default(sqldb01) +// @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/sqlDb/{sqlDbId} [delete] +func RestDeleteSqlDb(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()}) + } + + sqlDbId := c.Param("sqlDbId") + err = common.CheckString(sqlDbId) + if err != nil { + errMsg := fmt.Errorf("invalid sqlDbId (%s)", sqlDbId) + log.Warn().Err(err).Msgf(errMsg.Error()) + return c.JSON(http.StatusBadRequest, model.SimpleMsg{Message: errMsg.Error()}) + } + + resp, err := resource.DeleteSqlDb(nsId, sqlDbId) + if err != nil { + log.Err(err).Msg("") + return c.JSON(http.StatusInternalServerError, model.SimpleMsg{Message: err.Error()}) + } + + return c.JSON(http.StatusOK, resp) +} + +// // RestPutSqlDb godoc +// // @ID PutSqlDb +// // @Summary (To be provided) Update the SQL database +// // @Description (To be provided) Update the SQL database +// // @Tags [Infra Resource] SQL Database 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 "SQL DB ID" default(sqldb01) +// // @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/{SqlDbId} [put] +// func RestPutSqlDb(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 +// } + +// RestGetRequestStatusOfSqlDb godoc +// @ID GetRequestStatusOfSqlDb +// @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] SQL Database Management (under development) +// @Accept json +// @Produce json +// @Param nsId path string true "Namespace ID" default(default) +// @Param sqlDbId path string true "SQL DB ID" default(sqldb01) +// @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/sqlDb/{sqlDbId}/request/{requestId} [get] +func RestGetRequestStatusOfSqlDb(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()}) + } + + sqlDbId := c.Param("sqlDbId") + err = common.CheckString(sqlDbId) + if err != nil { + errMsg := fmt.Errorf("invalid sqlDbId (%s)", sqlDbId) + 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.GetRequestStatusOfSqlDb(nsId, sqlDbId, 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/vpn.go b/src/api/rest/server/resource/vpn.go index 479083de..ed8b1980 100644 --- a/src/api/rest/server/resource/vpn.go +++ b/src/api/rest/server/resource/vpn.go @@ -338,7 +338,7 @@ func RestPostSiteToSiteVpn(c echo.Context) error { } // Validate the VPN sites - ok, err := resource.IsValidCspSet(vpnReq.Site1.CSP, vpnReq.Site2.CSP) + ok, err := resource.IsValidCspSetForVPN(vpnReq.Site1.CSP, vpnReq.Site2.CSP) if !ok { log.Warn().Err(err).Msg("") res := model.SimpleMsg{ diff --git a/src/api/rest/server/server.go b/src/api/rest/server/server.go index bbfd7f9c..2422638c 100644 --- a/src/api/rest/server/server.go +++ b/src/api/rest/server/server.go @@ -496,6 +496,17 @@ func RunServer() { g.POST("/:nsId/registerCspResource/vNet/:vNetId/subnet", rest_resource.RestPostRegisterSubnet) g.DELETE("/:nsId/deregisterCspResource/vNet/:vNetId/subnet/:subnetId", rest_resource.RestDeleteDeregisterSubnet) + // SQL database management + // g.GET("/:nsId/resources/sqlDb", rest_resource.) + 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) + /* g.POST("/:nsId/resources/publicIp", resource.RestPostPublicIp) g.GET("/:nsId/resources/publicIp/:publicIpId", resource.RestGetPublicIp) diff --git a/src/core/common/utility.go b/src/core/common/utility.go index c2afeaa4..72183d31 100644 --- a/src/core/common/utility.go +++ b/src/core/common/utility.go @@ -222,6 +222,7 @@ func GenResourceKey(nsId string, resourceType string, resourceId string) string resourceType == model.StrSpec || resourceType == model.StrVNet || resourceType == model.StrVPN || + resourceType == model.StrSqlDB || resourceType == model.StrSecurityGroup || resourceType == model.StrDataDisk { //resourceType == "publicIp" || diff --git a/src/core/model/common.go b/src/core/model/common.go index 32d15f18..3a932487 100644 --- a/src/core/model/common.go +++ b/src/core/model/common.go @@ -100,6 +100,7 @@ const ( StrVNet string = "vNet" StrSubnet string = "subnet" StrVPN string = "vpn" + StrSqlDB string = "sqlDb" StrDataDisk string = "dataDisk" StrNLB string = "nlb" StrVM string = "vm" diff --git a/src/core/model/sqlDb.go b/src/core/model/sqlDb.go new file mode 100644 index 00000000..dfd233a7 --- /dev/null +++ b/src/core/model/sqlDb.go @@ -0,0 +1,68 @@ +/* +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 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" validate:"required" example:"db.t3.micro"` + DBEnginePort int `json:"dbEnginePort,omitempty" validate:"required" example:"3306"` + DBEngineVersion string `json:"dbEngineVersion" 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 RequiredCSPResource struct { + AWS RequiredAWSResource `json:"aws,omitempty"` + Azure RequiredAzureResource `json:"azure,omitempty"` + NCP RequiredNCPResource `json:"ncp,omitempty"` +} + +type RequiredAWSResource 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 { + ResourceGroup string `json:"resourceGroup,omitempty" example:"rg-xxxx"` +} + +type RequiredNCPResource struct { + SubnetID string `json:"subnetID,omitempty" example:"subnet-xxxx"` +} + +type SqlDBInfo 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/resource/common.go b/src/core/resource/common.go index adbbff6e..a0b72a5c 100644 --- a/src/core/resource/common.go +++ b/src/core/resource/common.go @@ -1132,6 +1132,7 @@ func CheckResource(nsId string, resourceType string, resourceId string) (bool, e resourceType == model.StrSpec || resourceType == model.StrVNet || resourceType == model.StrVPN || + resourceType == model.StrSqlDB || resourceType == model.StrSecurityGroup || resourceType == model.StrDataDisk { //resourceType == "subnet" || diff --git a/src/core/resource/sqlDb.go b/src/core/resource/sqlDb.go new file mode 100644 index 00000000..dc6e4acb --- /dev/null +++ b/src/core/resource/sqlDb.go @@ -0,0 +1,1166 @@ +/* +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 ( + "context" + "encoding/json" + "fmt" + "os" + "time" + + "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" +) + +// SqlDBStatus represents the status of a network resource. +type SqlDBStatus string + +const ( + + // CRUD operations + SqlDBOnConfiguring SqlDBStatus = "Configuring" // Resources are being configured. + // SqlDBOnReading SqlDBStatus = "Reading" // The network information is being read. + // SqlDBOnUpdating SqlDBStatus = "Updating" // The network is being updated. + SqlDBOnDeleting SqlDBStatus = "Deleting" // The network is being deleted. + // // NetworkOnRefinining SqlDBStatus = "Refining" // The network is being refined. + + // // Register/deregister operations + // SqlDBOnRegistering SqlDBStatus = "Registering" // The network is being registered. + // SqlDBOnDeregistering SqlDBStatus = "Dergistering" // The network is being registered. + + // NetworkAvailable status + SqlDBAvailable SqlDBStatus = "Available" // The network is fully created and ready for use. + + // // In Use status + // SqlDBInUse SqlDBStatus = "InUse" // The network is currently in use. + + // // Unknwon status + // SqlDBUnknown SqlDBStatus = "Unknown" // The network status is unknown. + + // // NetworkError Handling + // SqlDBError SqlDBStatus = "Error" // An error occurred during a CRUD operation. + // SqlDBErrorOnConfiguring SqlDBStatus = "ErrorOnConfiguring" // An error occurred during the configuring operation. + // SqlDBErrorOnReading SqlDBStatus = "ErrorOnReading" // An error occurred during the reading operation. + // SqlDBErrorOnUpdating SqlDBStatus = "ErrorOnUpdating" // An error occurred during the updating operation. + // SqlDBErrorOnDeleting SqlDBStatus = "ErrorOnDeleting" // An error occurred during the deleting operation. + // SqlDBErrorOnRegistering SqlDBStatus = "ErrorOnRegistering" // An error occurred during the registering operation. +) + +type SqlDBAction string + +var validCspForSqlDB = map[string]bool{ + "aws": true, + "azure": true, + "gcp": true, + "ncp": true, + // "alibaba": true, + // "nhn": true, + // "kt": true, + + // Add more CSPs here +} + +func IsValidCspForSqlDB(csp string) (bool, error) { + if !validCspForSqlDB[csp] { + return false, fmt.Errorf("currently not supported CSP, %s", csp) + } + return true, nil +} + +// func whichCspForSqlDB(csp1, csp2 string) string { +// return csp1 + "," + csp2 +// } + +// 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 + var sqlDBInfo model.SqlDBInfo + 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(sqlDbReq.Name) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + ok, err := IsValidCspForSqlDB(sqlDbReq.CSP) + if !ok { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Check the CSPs of the sites + switch sqlDbReq.CSP { + case "aws": + // Check the required CSP resources + if sqlDbReq.RequiredCSPResource.AWS.VNetID == "" { + err = fmt.Errorf("required AWS VNetID is empty") + log.Error().Err(err).Msg("") + return emptyRet, err + } + if sqlDbReq.RequiredCSPResource.AWS.Subnet1ID == "" { + err = fmt.Errorf("required AWS subnet1ID is empty") + log.Error().Err(err).Msg("") + return emptyRet, err + } + if sqlDbReq.RequiredCSPResource.AWS.Subnet2ID == "" { + err = fmt.Errorf("required AWS subnet2ID is empty") + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // TODO: Check if the subnets are in the different AZs + // + + case "ncp": + // Check the required CSP resources + if sqlDbReq.RequiredCSPResource.NCP.SubnetID == "" { + err = fmt.Errorf("required NCP subnetID is empty") + log.Error().Err(err).Msg("") + return emptyRet, err + } + } + + // Set the resource type + resourceType := model.StrSqlDB + + // Set the SQL DB object in advance + uid := common.GenUid() + sqlDBInfo.ResourceType = resourceType + sqlDBInfo.Name = sqlDbReq.Name + sqlDBInfo.Id = sqlDbReq.Name + sqlDBInfo.Uid = uid + sqlDBInfo.Description = "SQL DB at " + sqlDbReq.Region + " in " + sqlDbReq.CSP + sqlDBInfo.ConnectionName = sqlDbReq.ConnectionName + sqlDBInfo.ConnectionConfig, err = common.GetConnConfig(sqlDBInfo.ConnectionName) + if err != nil { + err = fmt.Errorf("Cannot retrieve ConnectionConfig" + err.Error()) + log.Error().Err(err).Msg("") + } + + // Set a sqlDBKey for the SQL DB object + sqlDBKey := common.GenResourceKey(nsId, resourceType, sqlDBInfo.Id) + // Check if the SQL DB resource already exists or not + exists, err := CheckResource(nsId, resourceType, sqlDBInfo.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, sqlDBInfo.Id) + return emptyRet, err + } + // For retry, read the stored SQL DB info if exists + if exists { + if !retried { + err := fmt.Errorf("already exists, SQL DB: %s", sqlDBInfo.Id) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored SQL DB info + sqlDBKv, err := kvstore.GetKv(sqlDBKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(sqlDBKv.Value), &sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + } + + // [Set and store status] + sqlDBInfo.Status = string(SqlDBOnConfiguring) + val, err := json.Marshal(sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(sqlDBKey, string(val)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + log.Debug().Msgf("SQL DB Info(initial): %+v", sqlDBInfo) + + /* + * [Via Terrarium] Create a SQL DB + */ + + // 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 := sqlDBInfo.Uid + + // Check the CSPs of the sites + switch sqlDbReq.CSP { + case "aws": + if !retried { + // Issue a terrarium + method := "POST" + url := fmt.Sprintf("%s/tr", epTerrarium) + reqTr := new(terrariumModel.TerrariumInfo) + reqTr.Id = trId + reqTr.Description = "SQL DB at " + sqlDbReq.Region + " in " + sqlDbReq.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/sql-db/env", epTerrarium, trId) + queryParams := "provider=" + sqlDbReq.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) + + // generate infracode + method = "POST" + url = fmt.Sprintf("%s/tr/%s/sql-db/infracode", epTerrarium, trId) + reqInfracode := new(terrariumModel.CreateInfracodeOfSqlDbRequest) + reqInfracode.TfVars.TerrariumID = trId + reqInfracode.TfVars.CSPRegion = sqlDbReq.Region + // reqInfracode.TfVars.CSPResourceGroup + reqInfracode.TfVars.DBInstanceSpec = sqlDbReq.DBInstanceSpec + reqInfracode.TfVars.DBEngineVersion = sqlDbReq.DBEngineVersion + reqInfracode.TfVars.DBAdminPassword = sqlDbReq.DBAdminPassword + reqInfracode.TfVars.DBAdminUsername = sqlDbReq.DBAdminUsername + reqInfracode.TfVars.CSPVNetID = sqlDbReq.RequiredCSPResource.AWS.VNetID + reqInfracode.TfVars.CSPSubnet1ID = sqlDbReq.RequiredCSPResource.AWS.Subnet1ID + reqInfracode.TfVars.CSPSubnet2ID = sqlDbReq.RequiredCSPResource.AWS.Subnet2ID + reqInfracode.TfVars.DBEnginePort = sqlDbReq.DBEnginePort + reqInfracode.TfVars.EgressCIDRBlock = "0.0.0.0/0" + reqInfracode.TfVars.IngressCIDRBlock = "0.0.0.0/0" + + 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) + + // check the infracode by plan + method = "POST" + url = fmt.Sprintf("%s/tr/%s/sql-db/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/sql-db", 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) + + /* + * [Via Terrarium] Retrieve the SQL DB info recursively until the SQL DB is created + */ + + // Recursively call the function to get the SQL DB info + // An expected completion duration is 15 minutes + expectedCompletionDuration := 15 * time.Minute + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) + defer cancel() + + ret, err := retrieveEnrichmentsInfoInTerrarium(ctx, trId, "sql-db", expectedCompletionDuration) + if err != nil { + log.Err(err).Msg("") + return emptyRet, err + } + + // Set the SQL DB info + var trSqlDBInfo terrariumModel.OutputAWSSqlDbInfo + jsonData, err := json.Marshal(ret.Object) + if err != nil { + log.Error().Err(err).Msg("") + } + err = json.Unmarshal(jsonData, &trSqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + } + + sqlDBInfo.CspResourceId = trSqlDBInfo.AWS.InstanceIdentifier + // sqlDBInfo.CspResourceName + sqlDBInfo.Details = trSqlDBInfo.AWS + + // case "azure,gcp": + + // if !retried { + // // issue a terrarium + // method := "POST" + // url := fmt.Sprintf("%s/tr", epTerrarium) + // reqTr := new(terrariumModel.TerrariumInfo) + // reqTr.Id = trId + // reqTr.Description = "VPN between GCP and Azure" + + // 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/vpn/gcp-azure/env", epTerrarium, trId) + // 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) + // } + + // // generate infracode + // method := "POST" + // url := fmt.Sprintf("%s/tr/%s/vpn/gcp-azure/infracode", epTerrarium, trId) + // reqInfracode := new(terrariumModel.CreateInfracodeOfGcpAzureVpnRequest) + + // if vpnReq.Site1.CSP == "azure" { + // // Site1 is Azure + // reqInfracode.TfVars.AzureRegion = vpnReq.Site1.Region + // reqInfracode.TfVars.AzureVirtualNetworkName = vpnReq.Site1.VNet + // reqInfracode.TfVars.AzureResourceGroupName = vpnReq.Site1.ResourceGroup + // reqInfracode.TfVars.AzureGatewaySubnetCidrBlock = vpnReq.Site1.GatewaySubnetCidr + // // Site2 is GCP + // reqInfracode.TfVars.GcpRegion = vpnReq.Site2.Region + // reqInfracode.TfVars.GcpVpcNetworkName = vpnReq.Site2.VNet + // } else { + // // Site1 is GCP + // reqInfracode.TfVars.GcpRegion = vpnReq.Site1.Region + // reqInfracode.TfVars.GcpVpcNetworkName = vpnReq.Site1.VNet + // // site2 is Azure + // reqInfracode.TfVars.AzureRegion = vpnReq.Site2.Region + // reqInfracode.TfVars.AzureVirtualNetworkName = vpnReq.Site2.VNet + // reqInfracode.TfVars.AzureResourceGroupName = vpnReq.Site2.ResourceGroup + // reqInfracode.TfVars.AzureGatewaySubnetCidrBlock = vpnReq.Site2.GatewaySubnetCidr + // } + + // 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) + + // // check the infracode by plan + // method = "POST" + // url = fmt.Sprintf("%s/tr/%s/vpn/gcp-azure/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/vpn/gcp-azure", 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) + + // /* + // * [Via Terrarium] Retrieve the VPN info recursively until the VPN is created + // */ + + // // Recursively call the function to get the VPN info + // // An expected completion duration is 15 minutes + // expectedCompletionDuration := 30 * time.Minute + + // ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) + // defer cancel() + + // ret, err := retrieveVPNInfo(ctx, trId, "gcp-azure", expectedCompletionDuration) + // if err != nil { + // log.Err(err).Msg("") + // return emptyRet, err + // } + + // // Set the VPN info + // var trVpnInfo terrariumModel.OutputGcpAzureVpnInfo + // jsonData, err := json.Marshal(ret.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 CSP: %s", sqlDbReq.CSP) + } + + // [Set and store status] + sqlDBInfo.Status = string(SqlDBAvailable) + + log.Debug().Msgf("SQL DB Info(final): %+v", sqlDBInfo) + + value, err := json.Marshal(sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(sqlDBKey, string(value)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Check if the SQL DB info is stored + sqlDBKv, err := kvstore.GetKv(sqlDBKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + if sqlDBKv == (kvstore.KeyValue{}) { + err := fmt.Errorf("does not exist, SQL DB: %s", sqlDBInfo.Id) + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(sqlDBKv.Value), &sqlDBInfo) + 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.StrSqlDB, + model.LabelId: sqlDBInfo.Id, + model.LabelName: sqlDBInfo.Name, + model.LabelUid: sqlDBInfo.Uid, + model.LabelCspResourceId: sqlDBInfo.CspResourceId, + model.LabelCspResourceName: sqlDBInfo.CspResourceName, + model.LabelStatus: sqlDBInfo.Status, + model.LabelDescription: sqlDBInfo.Description, + } + err = label.CreateOrUpdateLabel(model.StrSqlDB, sqlDBInfo.Uid, sqlDBKey, labels) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + return sqlDBInfo, nil +} + +// 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 + 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(sqlDbId) + 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.StrSqlDB + + // Set a sqlDBKey for the SQL DB object + sqlDBKey := common.GenResourceKey(nsId, resourceType, sqlDbId) + // Check if the SQL DB resource already exists or not + exists, err := CheckResource(nsId, resourceType, sqlDbId) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the SQL DB(%s) exists or not", sqlDbId) + return emptyRet, err + } + if !exists { + err := fmt.Errorf("does not exist, SQL DB: %s", sqlDbId) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored SQL DB info + sqlDBKv, err := kvstore.GetKv(sqlDBKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(sqlDBKv.Value), &sqlDBInfo) + 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 := sqlDBInfo.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. "sql-db" + 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 + } + + // switch enrichments { + // case "vpn/gcp-aws": + var trVpnInfo terrariumModel.OutputAWSSqlDbInfo + 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.CspResourceId = trVpnInfo.AWS.InstanceIdentifier + sqlDBInfo.Details = trVpnInfo.AWS + + // 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) + // } + + log.Debug().Msgf("SQL DB Info(final): %+v", sqlDBInfo) + + value, err := json.Marshal(sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(sqlDBKey, string(value)) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Check if the SQL DB info is stored + sqlDBKv, err = kvstore.GetKv(sqlDBKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + if sqlDBKv == (kvstore.KeyValue{}) { + err := fmt.Errorf("does not exist, SQL DB: %s", sqlDBInfo.Id) + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(sqlDBKv.Value), &sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + return sqlDBInfo, nil +} + +// DeleteSqlDb deletes a SQL database via Terrarium +func DeleteSqlDb(nsId string, sqlDbId string) (model.SimpleMsg, error) { + + // VPN objects + var emptyRet model.SimpleMsg + var sqlDBInfo model.SqlDBInfo + 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(sqlDbId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Set the resource type + resourceType := model.StrSqlDB + + // Set a sqlDbKey for the SQL DB object + sqlDbKey := common.GenResourceKey(nsId, resourceType, sqlDbId) + // Check if the SQL DB resource already exists or not + exists, err := CheckResource(nsId, resourceType, sqlDbId) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the SQL DB (%s) exists or not", sqlDbId) + return emptyRet, err + } + if !exists { + err := fmt.Errorf("does not exist, SQL DB: %s", sqlDbId) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored SQL DB info + sqlDBKv, err := kvstore.GetKv(sqlDbKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(sqlDBKv.Value), &sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // [Set and store status] + sqlDBInfo.Status = string(SqlDBOnDeleting) + val, err := json.Marshal(sqlDBInfo) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = kvstore.Put(sqlDbKey, 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 := sqlDBInfo.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(sqlDbKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Remove label info using RemoveLabel + err = label.RemoveLabel(model.StrSqlDB, sqlDBInfo.Uid, sqlDbKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + res := model.SimpleMsg{ + Message: resDeleteTr.Message, + } + + return res, nil +} + +// 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 + 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(sqlDbId) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Set the resource type + resourceType := model.StrSqlDB + + // Set a sqlDBKey for the SQL DB object + sqlDBKey := common.GenResourceKey(nsId, resourceType, sqlDbId) + // Check if the SQL DB resource already exists or not + exists, err := CheckResource(nsId, resourceType, sqlDbId) + if err != nil { + log.Error().Err(err).Msg("") + err := fmt.Errorf("failed to check if the SQL DB(%s) exists or not", sqlDbId) + return emptyRet, err + } + if !exists { + err := fmt.Errorf("does not exist, SQL DB: %s", sqlDbId) + log.Error().Err(err).Msg("") + return emptyRet, err + } + + // Read the stored SQL DB info + sqlDBKv, err := kvstore.GetKv(sqlDBKey) + if err != nil { + log.Error().Err(err).Msg("") + return emptyRet, err + } + err = json.Unmarshal([]byte(sqlDBKv.Value), &sqlDBInfo) + 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 := sqlDBInfo.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/vpn.go b/src/core/resource/vpn.go index 94c3db75..2bb2609a 100644 --- a/src/core/resource/vpn.go +++ b/src/core/resource/vpn.go @@ -37,7 +37,7 @@ const ( maxWaitDuration = 120 * time.Second ) -var validCspSet = map[string]bool{ +var validCspSetForVPN = map[string]bool{ "aws,gcp": true, "gcp,aws": true, "gcp,azure": true, @@ -50,14 +50,14 @@ var validCspSet = map[string]bool{ // Add more CSP sets here } -func IsValidCspSet(csp1, csp2 string) (bool, error) { - if !validCspSet[csp1+","+csp2] { +func IsValidCspSetForVPN(csp1, csp2 string) (bool, error) { + if !validCspSetForVPN[csp1+","+csp2] { return false, fmt.Errorf("currently not supported, VPN between %s and %s", csp1, csp2) } return true, nil } -func whichCspSet(csp1, csp2 string) string { +func whichCspSetForVPN(csp1, csp2 string) string { return csp1 + "," + csp2 } @@ -184,7 +184,7 @@ func CreateSiteToSiteVPN(nsId string, mciId string, vpnReq *model.RestPostVpnReq log.Error().Err(err).Msg("") return emptyRet, err } - ok, err := IsValidCspSet(vpnReq.Site1.CSP, vpnReq.Site2.CSP) + ok, err := IsValidCspSetForVPN(vpnReq.Site1.CSP, vpnReq.Site2.CSP) if !ok { log.Error().Err(err).Msg("") return emptyRet, err @@ -285,7 +285,7 @@ func CreateSiteToSiteVPN(nsId string, mciId string, vpnReq *model.RestPostVpnReq // Set a terrarium ID trId := vpnInfo.Uid - cspSet := whichCspSet(vpnReq.Site1.CSP, vpnReq.Site2.CSP) + cspSet := whichCspSetForVPN(vpnReq.Site1.CSP, vpnReq.Site2.CSP) // Check the CSPs of the sites switch cspSet { @@ -451,7 +451,7 @@ func CreateSiteToSiteVPN(nsId string, mciId string, vpnReq *model.RestPostVpnReq ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) defer cancel() - ret, err := retrieveVPNInfo(ctx, trId, "gcp-aws", expectedCompletionDuration) + ret, err := retrieveEnrichmentsInfoInTerrarium(ctx, trId, "vpn/gcp-aws", expectedCompletionDuration) if err != nil { log.Err(err).Msg("") return emptyRet, err @@ -642,7 +642,7 @@ func CreateSiteToSiteVPN(nsId string, mciId string, vpnReq *model.RestPostVpnReq ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) defer cancel() - ret, err := retrieveVPNInfo(ctx, trId, "gcp-azure", expectedCompletionDuration) + ret, err := retrieveEnrichmentsInfoInTerrarium(ctx, trId, "vpn/gcp-azure", expectedCompletionDuration) if err != nil { log.Err(err).Msg("") return emptyRet, err @@ -723,7 +723,7 @@ func CreateSiteToSiteVPN(nsId string, mciId string, vpnReq *model.RestPostVpnReq return vpnInfo, nil } -func retrieveVPNInfo(ctx context.Context, trId string, cspPair string, expectedCompletionDuration time.Duration) (model.Response, error) { +func retrieveEnrichmentsInfoInTerrarium(ctx context.Context, trId string, enrichments string, expectedCompletionDuration time.Duration) (model.Response, error) { var emptyRet model.Response @@ -750,7 +750,7 @@ func retrieveVPNInfo(ctx context.Context, trId string, cspPair string, expectedC err := common.ExecuteHttpRequest( client, "GET", - fmt.Sprintf("%s/tr/%s/vpn/%s?detail=refined", epTerrarium, trId, cspPair), + fmt.Sprintf("%s/tr/%s/%s?detail=refined", epTerrarium, trId, enrichments), nil, common.SetUseBody(requestBody), &requestBody, @@ -759,7 +759,7 @@ func retrieveVPNInfo(ctx context.Context, trId string, cspPair string, expectedC ) if err == nil { - log.Info().Msg("VPN retrieval successful.") + log.Info().Msgf("successfully retrieve the enrichments (%s)", enrichments) return *resRetrieving, nil } @@ -768,7 +768,7 @@ func retrieveVPNInfo(ctx context.Context, trId string, cspPair string, expectedC minutes := int(elapsedTime.Minutes()) seconds := int(elapsedTime.Seconds()) % 60 - log.Info().Msgf("[Elapsed time: %dm%ds] Creating VPN, retrying in %s...", minutes, seconds, currentWaitDuration) + log.Info().Msgf("[Elapsed time: %dm%ds] Creating enrichments (%s), retrying in %s...", minutes, seconds, enrichments, currentWaitDuration) select { case <-ctx.Done():