From b7d91a78be817722f13397bac4a507ed8baecf5a Mon Sep 17 00:00:00 2001 From: Yunkon Kim Date: Mon, 11 Nov 2024 17:29:39 +0900 Subject: [PATCH] Add API to CRUD a SQL database instance * It's only tested on AWS. --- api/docs.go | 555 +++++++++++++++++ api/swagger.json | 555 +++++++++++++++++ api/swagger.yaml | 377 ++++++++++++ examples/aws/mysql-db-instance/main.tf | 1 - pkg/api/rest/handler/sql-db.go | 822 +++++++++++++++++++++++++ pkg/api/rest/model/request.go | 5 + pkg/api/rest/model/sql-db.go | 18 + pkg/api/rest/server.go | 22 +- pkg/tofu/tofu.go | 14 + templates/sql-db/aws/output.tf | 15 + templates/sql-db/aws/providers.tf | 18 + templates/sql-db/aws/sql-db.tf | 60 ++ templates/sql-db/aws/variables.tf | 86 +++ 13 files changed, 2541 insertions(+), 7 deletions(-) create mode 100644 pkg/api/rest/handler/sql-db.go create mode 100644 pkg/api/rest/model/sql-db.go create mode 100644 templates/sql-db/aws/output.tf create mode 100644 templates/sql-db/aws/providers.tf create mode 100644 templates/sql-db/aws/sql-db.tf create mode 100644 templates/sql-db/aws/variables.tf diff --git a/api/docs.go b/api/docs.go index 492f7a0..2f4877c 100644 --- a/api/docs.go +++ b/api/docs.go @@ -833,6 +833,496 @@ const docTemplate = `{ } } }, + "/tr/{trId}/sql-db": { + "get": { + "description": "Get resource info of SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Get resource info of SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "refined", + "description": "Resource info by detail (refined, raw)", + "name": "detail", + "in": "query" + }, + { + "type": "string", + "description": "custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + }, + "post": { + "description": "Create SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Create SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + }, + "delete": { + "description": "Destroy SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Destroy SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/env": { + "post": { + "description": "Initialize a multi-cloud terrarium for SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Initialize a multi-cloud terrarium for SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "enum": [ + "aws", + "azure", + "gcp", + "ncp" + ], + "type": "string", + "default": "aws", + "description": "Provider", + "name": "provider", + "in": "query" + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + }, + "delete": { + "description": "Clear the entire directory and configuration files", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Clear the entire directory and configuration files", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "enum": [ + "force" + ], + "type": "string", + "default": "", + "description": "Action", + "name": "action", + "in": "query" + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/infracode": { + "post": { + "description": "Create the infracode for SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Create the infracode for SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "description": "Parameters of infracode for SQL database", + "name": "ParamsForInfracode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.CreateInfracodeOfSqlDbRequest" + } + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/plan": { + "post": { + "description": "Check and show changes by the current infracode", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Check and show changes by the current infracode", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/request/{requestId}": { + "get": { + "description": "Check the status of a specific request by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Check the status of a specific request by its ID", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "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.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, "/tr/{trId}/vpn/gcp-aws": { "get": { "description": "Get resource info to configure GCP to AWS VPN tunnels", @@ -1880,6 +2370,14 @@ const docTemplate = `{ } } }, + "model.CreateInfracodeOfSqlDbRequest": { + "type": "object", + "properties": { + "tfVars": { + "$ref": "#/definitions/model.TfVarsSqlDb" + } + } + }, "model.CreateInfracodeOfTestEnvRequest": { "type": "object", "properties": { @@ -2023,6 +2521,63 @@ const docTemplate = `{ } } }, + "model.TfVarsSqlDb": { + "type": "object", + "properties": { + "csp_region": { + "type": "string", + "example": "ap-northeast-2" + }, + "csp_subnet1_id": { + "type": "string", + "example": "subnet-1234abcd" + }, + "csp_subnet2_id": { + "type": "string", + "example": "subnet-abcd1234" + }, + "csp_vnet_id": { + "type": "string", + "example": "vpc-12345678" + }, + "db_admin_password": { + "type": "string", + "example": "mysdbpass" + }, + "db_admin_username": { + "type": "string", + "example": "mydbadmin" + }, + "db_engine_port": { + "type": "integer", + "example": 3306 + }, + "db_engine_version": { + "type": "string", + "example": "8.0.39" + }, + "db_instance_class": { + "type": "string", + "example": "db.t3.micro" + }, + "db_instance_identifier": { + "type": "string", + "example": "mydbinstance" + }, + "egress_cidr_block": { + "type": "string", + "example": "0.0.0.0/0" + }, + "ingress_cidr_block": { + "type": "string", + "example": "0.0.0.0/0" + }, + "terrarium_id": { + "type": "string", + "example": "" + } + } + }, "model.TfVarsTestEnv": { "type": "object", "properties": { diff --git a/api/swagger.json b/api/swagger.json index 0f6921c..becaee7 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -827,6 +827,496 @@ } } }, + "/tr/{trId}/sql-db": { + "get": { + "description": "Get resource info of SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Get resource info of SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "refined", + "description": "Resource info by detail (refined, raw)", + "name": "detail", + "in": "query" + }, + { + "type": "string", + "description": "custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + }, + "post": { + "description": "Create SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Create SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + }, + "delete": { + "description": "Destroy SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Destroy SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/env": { + "post": { + "description": "Initialize a multi-cloud terrarium for SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Initialize a multi-cloud terrarium for SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "enum": [ + "aws", + "azure", + "gcp", + "ncp" + ], + "type": "string", + "default": "aws", + "description": "Provider", + "name": "provider", + "in": "query" + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + }, + "delete": { + "description": "Clear the entire directory and configuration files", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Clear the entire directory and configuration files", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "enum": [ + "force" + ], + "type": "string", + "default": "", + "description": "Action", + "name": "action", + "in": "query" + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/infracode": { + "post": { + "description": "Create the infracode for SQL database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Create the infracode for SQL database", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "description": "Parameters of infracode for SQL database", + "name": "ParamsForInfracode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.CreateInfracodeOfSqlDbRequest" + } + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/plan": { + "post": { + "description": "Check and show changes by the current infracode", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Check and show changes by the current infracode", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Custom request ID", + "name": "x-request-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, + "/tr/{trId}/sql-db/request/{requestId}": { + "get": { + "description": "Check the status of a specific request by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[SQL Database] Operations" + ], + "summary": "Check the status of a specific request by its ID", + "parameters": [ + { + "type": "string", + "default": "tr01", + "description": "Terrarium ID", + "name": "trId", + "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.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/model.Response" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/model.Response" + } + } + } + } + }, "/tr/{trId}/vpn/gcp-aws": { "get": { "description": "Get resource info to configure GCP to AWS VPN tunnels", @@ -1874,6 +2364,14 @@ } } }, + "model.CreateInfracodeOfSqlDbRequest": { + "type": "object", + "properties": { + "tfVars": { + "$ref": "#/definitions/model.TfVarsSqlDb" + } + } + }, "model.CreateInfracodeOfTestEnvRequest": { "type": "object", "properties": { @@ -2017,6 +2515,63 @@ } } }, + "model.TfVarsSqlDb": { + "type": "object", + "properties": { + "csp_region": { + "type": "string", + "example": "ap-northeast-2" + }, + "csp_subnet1_id": { + "type": "string", + "example": "subnet-1234abcd" + }, + "csp_subnet2_id": { + "type": "string", + "example": "subnet-abcd1234" + }, + "csp_vnet_id": { + "type": "string", + "example": "vpc-12345678" + }, + "db_admin_password": { + "type": "string", + "example": "mysdbpass" + }, + "db_admin_username": { + "type": "string", + "example": "mydbadmin" + }, + "db_engine_port": { + "type": "integer", + "example": 3306 + }, + "db_engine_version": { + "type": "string", + "example": "8.0.39" + }, + "db_instance_class": { + "type": "string", + "example": "db.t3.micro" + }, + "db_instance_identifier": { + "type": "string", + "example": "mydbinstance" + }, + "egress_cidr_block": { + "type": "string", + "example": "0.0.0.0/0" + }, + "ingress_cidr_block": { + "type": "string", + "example": "0.0.0.0/0" + }, + "terrarium_id": { + "type": "string", + "example": "" + } + } + }, "model.TfVarsTestEnv": { "type": "object", "properties": { diff --git a/api/swagger.yaml b/api/swagger.yaml index 98fd410..7c7eec5 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -71,6 +71,11 @@ definitions: tfVars: $ref: '#/definitions/model.TfVarsGcpAzureVpnTunnel' type: object + model.CreateInfracodeOfSqlDbRequest: + properties: + tfVars: + $ref: '#/definitions/model.TfVarsSqlDb' + type: object model.CreateInfracodeOfTestEnvRequest: properties: tfVars: @@ -175,6 +180,48 @@ definitions: example: "" type: string type: object + model.TfVarsSqlDb: + properties: + csp_region: + example: ap-northeast-2 + type: string + csp_subnet1_id: + example: subnet-1234abcd + type: string + csp_subnet2_id: + example: subnet-abcd1234 + type: string + csp_vnet_id: + example: vpc-12345678 + type: string + db_admin_password: + example: mysdbpass + type: string + db_admin_username: + example: mydbadmin + type: string + db_engine_port: + example: 3306 + type: integer + db_engine_version: + example: 8.0.39 + type: string + db_instance_class: + example: db.t3.micro + type: string + db_instance_identifier: + example: mydbinstance + type: string + egress_cidr_block: + example: 0.0.0.0/0 + type: string + ingress_cidr_block: + example: 0.0.0.0/0 + type: string + terrarium_id: + example: "" + type: string + type: object model.TfVarsTestEnv: properties: aws-region: @@ -752,6 +799,336 @@ paths: summary: Read a terrarium tags: - '[Terrarium] An environment to enrich the multi-cloud infrastructure' + /tr/{trId}/sql-db: + delete: + consumes: + - application/json + description: Destroy SQL database + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - description: Custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Destroy SQL database + tags: + - '[SQL Database] Operations' + get: + consumes: + - application/json + description: Get resource info of SQL database + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - default: refined + description: Resource info by detail (refined, raw) + in: query + name: detail + type: string + - description: custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Get resource info of SQL database + tags: + - '[SQL Database] Operations' + post: + consumes: + - application/json + description: Create SQL database + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - description: Custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Create SQL database + tags: + - '[SQL Database] Operations' + /tr/{trId}/sql-db/env: + delete: + consumes: + - application/json + description: Clear the entire directory and configuration files + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - default: "" + description: Action + enum: + - force + in: query + name: action + type: string + - description: Custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Clear the entire directory and configuration files + tags: + - '[SQL Database] Operations' + post: + consumes: + - application/json + description: Initialize a multi-cloud terrarium for SQL database + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - default: aws + description: Provider + enum: + - aws + - azure + - gcp + - ncp + in: query + name: provider + type: string + - description: Custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Initialize a multi-cloud terrarium for SQL database + tags: + - '[SQL Database] Operations' + /tr/{trId}/sql-db/infracode: + post: + consumes: + - application/json + description: Create the infracode for SQL database + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - description: Parameters of infracode for SQL database + in: body + name: ParamsForInfracode + required: true + schema: + $ref: '#/definitions/model.CreateInfracodeOfSqlDbRequest' + - description: Custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Create the infracode for SQL database + tags: + - '[SQL Database] Operations' + /tr/{trId}/sql-db/plan: + post: + consumes: + - application/json + description: Check and show changes by the current infracode + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - description: Custom request ID + in: header + name: x-request-id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Check and show changes by the current infracode + tags: + - '[SQL Database] Operations' + /tr/{trId}/sql-db/request/{requestId}: + get: + consumes: + - application/json + description: Check the status of a specific request by its ID + parameters: + - default: tr01 + description: Terrarium ID + in: path + name: trId + required: true + type: string + - description: Request ID + in: path + name: requestId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/model.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/model.Response' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/model.Response' + summary: Check the status of a specific request by its ID + tags: + - '[SQL Database] Operations' /tr/{trId}/vpn/gcp-aws: delete: consumes: diff --git a/examples/aws/mysql-db-instance/main.tf b/examples/aws/mysql-db-instance/main.tf index 93f0c9c..25e6b42 100644 --- a/examples/aws/mysql-db-instance/main.tf +++ b/examples/aws/mysql-db-instance/main.tf @@ -17,7 +17,6 @@ provider "aws" { region = "ap-northeast-2" } - # Create a VPC resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" diff --git a/pkg/api/rest/handler/sql-db.go b/pkg/api/rest/handler/sql-db.go new file mode 100644 index 0000000..75ccd5b --- /dev/null +++ b/pkg/api/rest/handler/sql-db.go @@ -0,0 +1,822 @@ +/* +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 handler + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + + "github.com/cloud-barista/mc-terrarium/pkg/api/rest/model" + "github.com/cloud-barista/mc-terrarium/pkg/config" + "github.com/cloud-barista/mc-terrarium/pkg/terrarium" + "github.com/cloud-barista/mc-terrarium/pkg/tofu" + "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" + "github.com/tidwall/gjson" +) + +var validProvidersForSqlDb = map[string]bool{ + "aws": true, + "azure": true, + "gcp": true, + "ncp": true, +} + +// InitEnvForSqlDb godoc +// @Summary Initialize a multi-cloud terrarium for SQL database +// @Description Initialize a multi-cloud terrarium for SQL database +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param provider query string false "Provider" Enums(aws, azure, gcp, ncp) default(aws) +// @Param x-request-id header string false "Custom request ID" +// @Success 201 {object} model.Response "Created" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db/env [post] +func InitEnvForSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + provider := c.QueryParam("provider") + if provider == "" { + err := fmt.Errorf("invalid request, provider is required") + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + if !validProvidersForSqlDb[provider] { + err := fmt.Errorf("invalid request, provider must be one of [aws, azure, gcp, ncp]") + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + // Get the request ID + reqId := c.Response().Header().Get(echo.HeaderXRequestID) + + // Set the enrichments + enrichments := "sql-db" + + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + trInfo.Enrichments = enrichments + err = terrarium.UpdateTerrariumInfo(trInfo) + if err != nil { + err2 := fmt.Errorf("failed to update terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Create a working directory for the terrarium + projectRoot := config.Terrarium.Root + workingDir := projectRoot + "/.terrarium/" + trId + "/" + enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err := os.MkdirAll(workingDir, 0755) + if err != nil { + err2 := fmt.Errorf("failed to create a working directory") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + } + + // Copy template files to the working directory (overwrite) + templateTfsPath := projectRoot + "/templates/" + enrichments + "/" + provider + + err = tofu.CopyFiles(templateTfsPath, workingDir) + if err != nil { + err2 := fmt.Errorf("failed to copy template files to working directory") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + if provider == "gcp" { + // Always overwrite credential-gcp.json + credentialPath := workingDir + "/credential-gcp.json" + + err = tofu.CopyGCPCredentials(credentialPath) + if err != nil { + err2 := fmt.Errorf("failed to copy gcp credentials") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + } + + // global option to set working dir: -chdir=/home/ubuntu/dev/cloud-barista/mc-terrarium/.terrarium/{trId}/vpn/gcp-aws + // init: subcommand + ret, err := tofu.ExecuteTofuCommand(trId, reqId, "-chdir="+workingDir, "init") + if err != nil { + err2 := fmt.Errorf("failed to initialize an infrastructure terrarium") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + res := model.Response{ + Success: true, + Message: "the infrastructure terrarium is successfully initialized", + Detail: ret, + } + + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusCreated, res) +} + +// ClearEnvForSqlDb godoc +// @Summary Clear the entire directory and configuration files +// @Description Clear the entire directory and configuration files +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param action query string false "Action" Enums(force) default() +// @Param x-request-id header string false "Custom request ID" +// @Success 200 {object} model.Response "OK" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db/env [delete] +func ClearSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + projectRoot := config.Terrarium.Root + + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Check if the working directory exists + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + err = os.RemoveAll(workingDir) + if err != nil { + err2 := fmt.Errorf("failed to remove working directory and all configuration files") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + text := "successfully remove all in the working directory" + res := model.Response{ + Success: true, + Message: text, + } + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusOK, res) +} + +// CreateInfracodeForSqlDb godoc +// @Summary Create the infracode for SQL database +// @Description Create the infracode for SQL database +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param ParamsForInfracode body model.CreateInfracodeOfSqlDbRequest true "Parameters of infracode for SQL database" +// @Param x-request-id header string false "Custom request ID" +// @Success 201 {object} model.Response "Created" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db/infracode [post] +func CreateInfracodeForSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + req := new(model.CreateInfracodeOfSqlDbRequest) + if err := c.Bind(req); err != nil { + err2 := fmt.Errorf("invalid request format, %v", err) + log.Warn().Err(err).Msg("invalid request format") + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + log.Debug().Msgf("%+v", req) // debug + + projectRoot := config.Terrarium.Root + + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Check if the working directory exists + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + // Save the tfVars to a file + tfVarsPath := workingDir + "/terraform.tfvars.json" + // Note + // Terraform also automatically loads a number of variable definitions files + // if they are present: + // - Files named exactly terraform.tfvars or terraform.tfvars.json. + // - Any files with names ending in .auto.tfvars or .auto.tfvars.json. + + if req.TfVars.TerrariumID == "" { + log.Warn().Msgf("terrarium ID is not set, Use path param: %s", trId) // warn + req.TfVars.TerrariumID = trId + } + + err = tofu.SavTfVarsToFile(req.TfVars, tfVarsPath) + if err != nil { + err2 := fmt.Errorf("failed to save tfVars to a file") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + res := model.Response{ + Success: true, + Message: "the infracode for SQL database is Successfully created", + } + + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusCreated, res) +} + +// CheckInfracodeForSqlDb godoc +// @Summary Check and show changes by the current infracode +// @Description Check and show changes by the current infracode +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param x-request-id header string false "Custom request ID" +// @Success 200 {object} model.Response "OK" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db/plan [post] +func CheckInfracodeForSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + // Get the request ID + reqId := c.Response().Header().Get(echo.HeaderXRequestID) + + projectRoot := config.Terrarium.Root + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Check if the working directory exists + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + // global option to set working dir: -chdir=/home/ubuntu/dev/cloud-barista/mc-terrarium/.terrarium/{trId}/sql-db + // subcommand: plan + ret, err := tofu.ExecuteTofuCommand(trId, reqId, "-chdir="+workingDir, "plan") + if err != nil { + err2 := fmt.Errorf("encountered an issue during the infracode checking process") + log.Error().Err(err).Msg(err2.Error()) // error + res := model.Response{ + Success: false, + Message: err2.Error(), + Detail: ret, + } + return c.JSON(http.StatusInternalServerError, res) + } + res := model.Response{ + Success: true, + Message: "the infracode checking process is successfully completed", + Detail: ret, + } + + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusOK, res) +} + +// CreateSqlDb godoc +// @Summary Create SQL database +// @Description Create SQL database +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param x-request-id header string false "Custom request ID" +// @Success 201 {object} model.Response "Created" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db [post] +func CreateSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + // Get the request ID + reqId := c.Response().Header().Get(echo.HeaderXRequestID) + + projectRoot := config.Terrarium.Root + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Check if the working directory exists + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + // global option to set working dir: -chdir=/home/ubuntu/dev/cloud-barista/mc-terrarium/.terrarium/{trId}/vpn/gcp-aws + // subcommand: apply + ret, err := tofu.ExecuteTofuCommandAsync(trId, reqId, "-chdir="+workingDir, "apply", "-auto-approve") + if err != nil { + err2 := fmt.Errorf("failed, previous request in progress") + log.Error().Err(err).Msg(err2.Error()) // error + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + res := model.Response{ + Success: true, + Message: "the request (id: " + reqId + ") is successfully accepted and still deploying resource", + Detail: ret, + } + + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusCreated, res) +} + +// GetResourceInfoOfGcpAwsVpn godoc +// @Summary Get resource info of SQL database +// @Description Get resource info of SQL database +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param detail query string false "Resource info by detail (refined, raw)" default(refined) +// @Param x-request-id header string false "custom request ID" +// @Success 200 {object} model.Response "OK" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db [get] +func GetResourceInfoOfSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + // 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 + } + + // Get the request ID + reqId := c.Response().Header().Get(echo.HeaderXRequestID) + + projectRoot := config.Terrarium.Root + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Check if the working directory exists + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + // Get the resource info by the detail option + switch detail { + case DetailOptions.Refined: + // Code for handling "refined" detail option + + // global option to set working dir: -chdir=/home/ubuntu/dev/cloud-barista/mc-terrarium/.terrarium/{trId}/sql-db + // show: subcommand + ret, err := tofu.ExecuteTofuCommand(trId, reqId, "-chdir="+workingDir, "output", "-json", "sql_db_info") + if err != nil { + err2 := fmt.Errorf("failed to read resource info (detail: %s) specified as 'output' in the state file", DetailOptions.Refined) + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + var resourceInfo map[string]interface{} + err = json.Unmarshal([]byte(ret), &resourceInfo) + if err != nil { + log.Error().Err(err).Msg("") // error + res := model.Response{ + Success: false, + Message: "failed to unmarshal resource info", + } + return c.JSON(http.StatusInternalServerError, res) + } + + res := model.Response{ + Success: true, + Message: "refined read resource info (map)", + Object: resourceInfo, + } + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusOK, res) + + case DetailOptions.Raw: + // Code for handling "raw" detail option + + // global option to set working dir: -chdir=/home/ubuntu/dev/cloud-barista/mc-terrarium/.terrarium/{trId}/vpn/gcp-aws + // show: subcommand + // Get resource info from the state or plan file + ret, err := tofu.ExecuteTofuCommand(trId, reqId, "-chdir="+workingDir, "show", "-json") + if err != nil { + err2 := fmt.Errorf("failed to read resource info (detail: %s) from the state or plan file", DetailOptions.Raw) + log.Error().Err(err).Msg(err2.Error()) // error + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + // Parse the resource info + resourcesString := gjson.Get(ret, "values.root_module.resources").String() + if resourcesString == "" { + err2 := fmt.Errorf("could not find resource info (trId: %s)", trId) + log.Warn().Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusOK, res) + } + + var resourceInfoList []interface{} + err = json.Unmarshal([]byte(resourcesString), &resourceInfoList) + if err != nil { + log.Error().Err(err).Msg("") // error + res := model.Response{ + Success: false, + Message: "failed to unmarshal resource info", + } + return c.JSON(http.StatusInternalServerError, res) + } + + res := model.Response{ + Success: true, + Message: "raw resource info (list)", + List: resourceInfoList, + } + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusOK, res) + default: + err2 := fmt.Errorf("invalid detail option (%s)", detail) + log.Warn().Err(err2).Msg("") // warn + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } +} + +// DestroyGcpAwsVpn godoc +// @Summary Destroy SQL database +// @Description Destroy SQL database +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param x-request-id header string false "Custom request ID" +// @Success 200 {object} model.Response "OK" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db [delete] +func DestroySqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + // Get the request ID + reqId := c.Response().Header().Get(echo.HeaderXRequestID) + + projectRoot := config.Terrarium.Root + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + // Check if the working directory exists + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + // Destroy the infrastructure + // global option to set working dir: -chdir=/home/ubuntu/dev/cloud-barista/mc-terrarium/.terrarium/{trId} + // subcommand: destroy + ret, err := tofu.ExecuteTofuCommand(trId, reqId, "-chdir="+workingDir, "destroy", "-auto-approve") + if err != nil { + err2 := fmt.Errorf("failed, previous request in progress") + log.Error().Err(err).Msg(err2.Error()) // error + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + res := model.Response{ + Success: true, + Message: fmt.Sprintf("the destroying process is successfully completed (trId: %s, enrichments: %s)", trId, trInfo.Enrichments), + Detail: ret, + } + + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusCreated, res) +} + +// GetRequestStatusOfGcpAwsVpn godoc +// @Summary Check the status of a specific request by its ID +// @Description Check the status of a specific request by its ID +// @Tags [SQL Database] Operations +// @Accept json +// @Produce json +// @Param trId path string true "Terrarium ID" default(tr01) +// @Param requestId path string true "Request ID" +// @Success 200 {object} model.Response "OK" +// @Failure 400 {object} model.Response "Bad Request" +// @Failure 500 {object} model.Response "Internal Server Error" +// @Failure 503 {object} model.Response "Service Unavailable" +// @Router /tr/{trId}/sql-db/request/{requestId} [get] +func GetRequestStatusOfSqlDb(c echo.Context) error { + + trId := c.Param("trId") + if trId == "" { + err := fmt.Errorf("invalid request, terrarium ID (trId: %s) is required", trId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + reqId := c.Param("requestId") + if reqId == "" { + err := fmt.Errorf("invalid request, request ID (requestId: %s) is required", reqId) + log.Warn().Msg(err.Error()) + res := model.Response{ + Success: false, + Message: err.Error(), + } + return c.JSON(http.StatusBadRequest, res) + } + + projectRoot := config.Terrarium.Root + // Read and set the enrichments to terrarium information + trInfo, err := terrarium.ReadTerrariumInfo(trId) + if err != nil { + err2 := fmt.Errorf("failed to read terrarium information") + log.Error().Err(err).Msg(err2.Error()) + res := model.Response{Success: false, Message: err2.Error()} + return c.JSON(http.StatusInternalServerError, res) + } + + workingDir := projectRoot + "/.terrarium/" + trId + "/" + trInfo.Enrichments + if _, err := os.Stat(workingDir); os.IsNotExist(err) { + err2 := fmt.Errorf("working directory dose not exist") + log.Warn().Err(err).Msg(err2.Error()) + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + statusLogFile := fmt.Sprintf("%s/runningLogs/%s.log", workingDir, reqId) + + // Check the statusReport of the request + statusReport, err := tofu.GetRunningStatus(trId, statusLogFile) + if err != nil { + err2 := fmt.Errorf("failed to get the status of the request") + log.Error().Err(err).Msg(err2.Error()) // error + res := model.Response{ + Success: false, + Message: err2.Error(), + } + return c.JSON(http.StatusInternalServerError, res) + } + + res := model.Response{ + Success: true, + Message: "the status of a specific request", + Detail: statusReport, + } + + log.Debug().Msgf("%+v", res) // debug + + return c.JSON(http.StatusOK, res) +} diff --git a/pkg/api/rest/model/request.go b/pkg/api/rest/model/request.go index 3f4f02a..d2ebdc6 100644 --- a/pkg/api/rest/model/request.go +++ b/pkg/api/rest/model/request.go @@ -14,3 +14,8 @@ type CreateInfracodeOfGcpAzureVpnRequest struct { type CreateInfracodeOfTestEnvRequest struct { TfVars TfVarsTestEnv `json:"tfVars"` } + +// Request body for sql-db +type CreateInfracodeOfSqlDbRequest struct { + TfVars TfVarsSqlDb `json:"tfVars"` +} diff --git a/pkg/api/rest/model/sql-db.go b/pkg/api/rest/model/sql-db.go new file mode 100644 index 0000000..60e3125 --- /dev/null +++ b/pkg/api/rest/model/sql-db.go @@ -0,0 +1,18 @@ +package model + +// TfVarsSqlDb represents the configuration structure based on the Terraform variables +type TfVarsSqlDb struct { + TerrariumID string `json:"terrarium_id" default:"" example:""` + CSPRegion string `json:"csp_region" example:"ap-northeast-2"` + CSPVNetID string `json:"csp_vnet_id" example:"vpc-12345678"` + CSPSubnet1ID string `json:"csp_subnet1_id" example:"subnet-1234abcd"` + CSPSubnet2ID string `json:"csp_subnet2_id" example:"subnet-abcd1234"` + DBEnginePort int `json:"db_engine_port" example:"3306"` + IngressCIDRBlock string `json:"ingress_cidr_block" example:"0.0.0.0/0"` + EgressCIDRBlock string `json:"egress_cidr_block" example:"0.0.0.0/0"` + DBInstanceID string `json:"db_instance_identifier" example:"mydbinstance"` + DBEngineVersion string `json:"db_engine_version" example:"8.0.39"` + DBInstanceClass string `json:"db_instance_class" example:"db.t3.micro"` + DBAdminUsername string `json:"db_admin_username" example:"mydbadmin"` + DBAdminPassword string `json:"db_admin_password" example:"mysdbpass"` +} diff --git a/pkg/api/rest/server.go b/pkg/api/rest/server.go index 7e84f0d..b918c2c 100644 --- a/pkg/api/rest/server.go +++ b/pkg/api/rest/server.go @@ -199,15 +199,25 @@ func RunServer(port string) { e.GET("/terrarium/httpVersion", handler.HTTPVersion) e.GET("/terrarium/tofuVersion", handler.TofuVersion) - // A group for Multi-cloud Network APIs which has /terrarium as prefix - groupMultiCloudNetwork := e.Group("/terrarium") + // A terrarium group has /terrarium as prefix + groupTerrarium := e.Group("/terrarium") // Resource Group APIs - route.RegisterRoutesForTestEnv(groupMultiCloudNetwork) - route.RegisterRoutesForRG(groupMultiCloudNetwork) - route.RegisterRoutesForVPN(groupMultiCloudNetwork) + route.RegisterRoutesForTestEnv(groupTerrarium) + route.RegisterRoutesForRG(groupTerrarium) + route.RegisterRoutesForVPN(groupTerrarium) + + // SQL database APIs + groupTerrarium.POST("/tr/:trId/sql-db/env", handler.InitEnvForSqlDb) + groupTerrarium.DELETE("/tr/:trId/sql-db/env", handler.ClearSqlDb) + groupTerrarium.POST("/tr/:trId/sql-db/infracode", handler.CreateInfracodeForSqlDb) + groupTerrarium.POST("/tr/:trId/sql-db/plan", handler.CheckInfracodeForSqlDb) + groupTerrarium.POST("/tr/:trId/sql-db", handler.CreateSqlDb) + groupTerrarium.GET("/tr/:trId/sql-db", handler.GetResourceInfoOfSqlDb) + groupTerrarium.DELETE("/tr/:trId/sql-db", handler.DestroySqlDb) + groupTerrarium.GET("/tr/:trId/sql-db/request/:requestId", handler.GetRequestStatusOfSqlDb) // Sample API group (for developers to add new API) - groupSample := groupMultiCloudNetwork.Group("/sample") + groupSample := groupTerrarium.Group("/sample") route.RegisterSampleRoutes(groupSample) selfEndpoint := config.Terrarium.Self.Endpoint diff --git a/pkg/tofu/tofu.go b/pkg/tofu/tofu.go index 9efd45e..ef1ae2f 100644 --- a/pkg/tofu/tofu.go +++ b/pkg/tofu/tofu.go @@ -349,6 +349,20 @@ func CopyFiles(sourceDir, destDir string) error { return nil } +func SavTfVarsToFile(tfVars interface{}, filePath string) error { + tfVarsBytes, err := json.MarshalIndent(tfVars, "", " ") + if err != nil { + return err + } + + err = os.WriteFile(filePath, tfVarsBytes, 0644) + if err != nil { + return err + } + + return nil +} + func SaveGcpAwsTfVarsToFile(tfVars model.TfVarsGcpAwsVpnTunnel, filePath string) error { tfVarsBytes, err := json.MarshalIndent(tfVars, "", " ") if err != nil { diff --git a/templates/sql-db/aws/output.tf b/templates/sql-db/aws/output.tf new file mode 100644 index 0000000..2525d74 --- /dev/null +++ b/templates/sql-db/aws/output.tf @@ -0,0 +1,15 @@ +output "sql_db_info" { + value = { + db_instance_identifier = aws_db_instance.db_instance.identifier + db_instance_endpoint = aws_db_instance.db_instance.endpoint + db_instance_port = aws_db_instance.db_instance.port + db_instance_username = aws_db_instance.db_instance.username + db_instance_engine = aws_db_instance.db_instance.engine + db_instance_version = aws_db_instance.db_instance.engine_version + db_instance_vpc_id = var.csp_vnet_id + db_instance_subnet_ids = [var.csp_subnet1_id, var.csp_subnet2_id] + db_security_group_name = "${var.terrarium_id}-rds-sg" + } + + description = "Information for connecting to the MySQL RDS instance with dynamic variables." +} diff --git a/templates/sql-db/aws/providers.tf b/templates/sql-db/aws/providers.tf new file mode 100644 index 0000000..b791134 --- /dev/null +++ b/templates/sql-db/aws/providers.tf @@ -0,0 +1,18 @@ +# Define the required version of Terraform and the providers that will be used in the project +terraform { + # Required Tofu version + required_version = "~>1.8.3" + + required_providers { + # AWS provider is specified with its source and version + aws = { + source = "registry.opentofu.org/hashicorp/aws" + version = "~>5.42" + } + } +} + +# Provider block for AWS specifies the configuration for the provider +provider "aws" { + region = var.csp_region +} diff --git a/templates/sql-db/aws/sql-db.tf b/templates/sql-db/aws/sql-db.tf new file mode 100644 index 0000000..c386512 --- /dev/null +++ b/templates/sql-db/aws/sql-db.tf @@ -0,0 +1,60 @@ +# Create a DB subnet group +resource "aws_db_subnet_group" "rds" { + name = "main" + subnet_ids = [var.csp_subnet1_id, var.csp_subnet2_id] + + tags = { + Name = "${var.terrarium_id} My DB subnet group" + } +} + + +# Create a security group for RDS Database Instance +resource "aws_security_group" "rds_sg" { + name = "${var.terrarium_id}-rds-sg" + vpc_id = var.csp_vnet_id + + ingress { + description = "Allow MySQL traffic" + from_port = var.db_engine_port + to_port = var.db_engine_port + protocol = "tcp" + cidr_blocks = [var.ingress_cidr_block] + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = [var.egress_cidr_block] + } + + tags = { + Name = "${var.terrarium_id}-rds-sg" + } +} + +# Create an RDS Database Instance with updated instance class and engine version + +resource "aws_db_instance" "db_instance" { + engine = "mysql" + identifier = "${var.terrarium_id}-${var.db_instance_identifier}" + allocated_storage = 20 + engine_version = var.db_engine_version # Use a compatible version of MySQL + instance_class = var.db_instance_class # Updated to a supported instance class + username = var.db_admin_username + password = var.db_admin_password + parameter_group_name = "default.mysql8.0" + + db_subnet_group_name = aws_db_subnet_group.rds.name # Use the created DB subnet group + vpc_security_group_ids = [aws_security_group.rds_sg.id] + + skip_final_snapshot = true + publicly_accessible = true + + tags = { + Name = "${var.terrarium_id}-db-instance" + } +} + diff --git a/templates/sql-db/aws/variables.tf b/templates/sql-db/aws/variables.tf new file mode 100644 index 0000000..17ab53c --- /dev/null +++ b/templates/sql-db/aws/variables.tf @@ -0,0 +1,86 @@ +variable "terrarium_id" { + type = string + description = "Unique ID to distinguish and manage infrastructure." + + validation { + condition = var.terrarium_id != "" + error_message = "The terrarium ID must be set" + } +} + +####################################################################### +# Amazon Web Services (AWS) +variable "csp_region" { + type = string + description = "A region in AWS." + default = "ap-northeast-2" + # AWS regions mapping list: + # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html +} + +# Required network information +variable "csp_vnet_id" { + type = string + description = "The VPC ID in AWS." +} + +variable "csp_subnet1_id" { + type = string + description = "The subnet ID in AWS." +} + +variable "csp_subnet2_id" { + type = string + description = "The subnet ID in AWS." +} + +# Required security group information +variable "db_engine_port" { + type = number + description = "The port number for the database engine." + default = 3306 +} + +variable "ingress_cidr_block" { + type = string + description = "The CIDR block for ingress traffic." + default = "0.0.0.0/0" +} + +variable "egress_cidr_block" { + type = string + description = "The CIDR block for egress traffic." + default = "0.0.0.0/0" +} + +# Required database engine information +variable "db_instance_identifier" { + type = string + description = "The identifier for the database." + default = "mydbinstance" +} + +variable "db_engine_version" { + type = string + description = "The version of the database engine." + default = "8.0.39" +} + +variable "db_instance_class" { + type = string + description = "The instance class for the database." + default = "db.t3.micro" +} + +variable "db_admin_username" { + type = string + description = "The admin username for the database." + default = "mydbadmin" +} + +variable "db_admin_password" { + type = string + description = "The admin password for the database." + default = "mysdbpass" +} +