From fc8dc4179981266ed2d473f92cd316fa8cc5c005 Mon Sep 17 00:00:00 2001 From: liuhaoming Date: Wed, 12 Feb 2025 17:02:40 +0800 Subject: [PATCH] feat: add implementation of variable set --- api/openapispec/docs.go | 987 +++++++++++++++++- api/openapispec/swagger.json | 987 +++++++++++++++++- api/openapispec/swagger.yaml | 647 +++++++++++- pkg/domain/constant/global.go | 1 + pkg/domain/constant/variable.go | 9 + pkg/domain/constant/variable_set.go | 8 + pkg/domain/entity/variable.go | 59 ++ pkg/domain/entity/variable_set.go | 41 + pkg/domain/repository/repository.go | 30 + pkg/domain/request/stack_request.go | 18 - pkg/domain/request/variable_request.go | 82 ++ pkg/domain/request/variable_set_request.go | 59 ++ pkg/domain/response/variable_response.go | 10 + pkg/domain/response/variable_set_response.go | 15 + pkg/infra/persistence/types.go | 2 + pkg/infra/persistence/util.go | 24 + pkg/infra/persistence/variable.go | 147 +++ pkg/infra/persistence/variable_model.go | 52 + pkg/infra/persistence/variable_set.go | 146 +++ pkg/infra/persistence/variable_set_model.go | 44 + pkg/server/handler/variable/handler.go | 247 +++++ pkg/server/handler/variable/types.go | 20 + pkg/server/handler/variableset/handler.go | 321 ++++++ pkg/server/handler/variableset/types.go | 19 + pkg/server/manager/variable/types.go | 25 + pkg/server/manager/variable/util.go | 17 + .../manager/variable/variable_manager.go | 153 +++ pkg/server/manager/variableset/types.go | 26 + pkg/server/manager/variableset/util.go | 17 + .../variableset/variable_set_manager.go | 145 +++ pkg/server/route/route.go | 39 + 31 files changed, 4369 insertions(+), 28 deletions(-) create mode 100644 pkg/domain/constant/variable.go create mode 100644 pkg/domain/constant/variable_set.go create mode 100644 pkg/domain/entity/variable.go create mode 100644 pkg/domain/entity/variable_set.go create mode 100644 pkg/domain/request/variable_request.go create mode 100644 pkg/domain/request/variable_set_request.go create mode 100644 pkg/domain/response/variable_response.go create mode 100644 pkg/domain/response/variable_set_response.go create mode 100644 pkg/infra/persistence/variable.go create mode 100644 pkg/infra/persistence/variable_model.go create mode 100644 pkg/infra/persistence/variable_set.go create mode 100644 pkg/infra/persistence/variable_set_model.go create mode 100644 pkg/server/handler/variable/handler.go create mode 100644 pkg/server/handler/variable/types.go create mode 100644 pkg/server/handler/variableset/handler.go create mode 100644 pkg/server/handler/variableset/types.go create mode 100644 pkg/server/manager/variable/types.go create mode 100644 pkg/server/manager/variable/util.go create mode 100644 pkg/server/manager/variable/variable_manager.go create mode 100644 pkg/server/manager/variableset/types.go create mode 100644 pkg/server/manager/variableset/util.go create mode 100644 pkg/server/manager/variableset/variable_set_manager.go diff --git a/api/openapispec/docs.go b/api/openapispec/docs.go index 647cf0f07..a5104601e 100644 --- a/api/openapispec/docs.go +++ b/api/openapispec/docs.go @@ -3347,6 +3347,795 @@ const docTemplate = `{ } } }, + "/api/v1/variables": { + "get": { + "description": "List variable information", + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "List variables", + "operationId": "listVariables", + "parameters": [ + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "query" + }, + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "query" + }, + { + "type": "integer", + "description": "The current page to fetch. Default to 1", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The size of the page. Default to 10", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "description": "Which field to sort the list by. Default to id", + "name": "sortBy", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to sort the list in ascending order. Default to false", + "name": "ascending", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to list all the variables", + "name": "fetchAll", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PaginatedVariableResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "post": { + "description": "Create a new variable", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Create variable", + "operationId": "createVariable", + "parameters": [ + { + "description": "Created variable", + "name": "variable", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateVariableRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Variable" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variables/{variableSetName}/{variableName}": { + "get": { + "description": "Get variable information by variable name and the variable set it belongs", + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Get variable", + "operationId": "getVariable", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Variable" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "put": { + "description": "Update the specified variable with name and the variable set it belongs to", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Update variable", + "operationId": "updateVariable", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "path", + "required": true + }, + { + "description": "Updated variable", + "name": "variable", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateVariableRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Variable" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "delete": { + "description": "Delete the specified variable by name and the variable set it belongs to", + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Delete variable", + "operationId": "deleteVariable", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variablesets": { + "get": { + "description": "List variable set information", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "List variable sets", + "operationId": "listVariableSets", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "query" + }, + { + "type": "integer", + "description": "The current page to fetch. Default to 1", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The size of the page. Default to 10", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "description": "Which field to sort the list by. Default to id", + "name": "sortBy", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to sort the list in ascending order. Default to false", + "name": "ascending", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to list all the variable sets", + "name": "fetchAll", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PaginatedVariableSetResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "post": { + "description": "Create a new variable set", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Create variable set", + "operationId": "createVariableSet", + "parameters": [ + { + "description": "Created variable set", + "name": "variableSet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateVariableSetRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variablesets/matched": { + "get": { + "description": "List variable set information by label selectors", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "List variable sets by labels", + "operationId": "listVariableSetsByLabels", + "parameters": [ + { + "type": "string", + "description": "Label selectors to match variable sets", + "name": "selector", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PaginatedVariableSetResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variablesets/{variableSetName}": { + "get": { + "description": "Get variable set information by variable set name", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Get variable set", + "operationId": "getVariableSet", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "put": { + "description": "Update the specified variable set with name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Update variable set", + "operationId": "updateVariableSet", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "description": "Updated variable set", + "name": "variableSet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateVariableSetRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "delete": { + "description": "Delete the specified variable set by name", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Delete variable set", + "operationId": "deleteVariableSet", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, "/api/v1/workspaces": { "get": { "description": "List all workspaces", @@ -3974,18 +4763,18 @@ const docTemplate = `{ "constant.SourceProviderType": { "type": "string", "enum": [ + "git", "git", "github", "oci", - "local", - "git" + "local" ], "x-enum-varnames": [ + "DefaultSourceType", "SourceProviderTypeGit", "SourceProviderTypeGithub", "SourceProviderTypeOCI", - "SourceProviderTypeLocal", - "DefaultSourceType" + "SourceProviderTypeLocal" ] }, "constant.StackState": { @@ -4605,6 +5394,58 @@ const docTemplate = `{ } } }, + "entity.Variable": { + "type": "object", + "properties": { + "name": { + "description": "Name is the name of the variable.", + "type": "string" + }, + "type": { + "description": "Type is the text type of the variable.", + "allOf": [ + { + "$ref": "#/definitions/entity.VariableType" + } + ] + }, + "value": { + "description": "Value is the value of the variable.", + "type": "string" + }, + "variableSet": { + "description": "VariableSet is the variable set to which the variable belongs.", + "type": "string" + } + } + }, + "entity.VariableSet": { + "type": "object", + "properties": { + "labels": { + "description": "Labels clarifies the scope of the variable set.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name is the name of the variable set.", + "type": "string" + } + } + }, + "entity.VariableType": { + "type": "string", + "enum": [ + "PlainText", + "CipherText" + ], + "x-enum-varnames": [ + "PlainTextType", + "CipherTextType" + ] + }, "entity.Workspace": { "type": "object", "properties": { @@ -5366,6 +6207,55 @@ const docTemplate = `{ } } }, + "request.CreateVariableRequest": { + "type": "object", + "required": [ + "name", + "variableSet" + ], + "properties": { + "name": { + "description": "Name is the name of the variable.", + "type": "string" + }, + "type": { + "description": "Type is the type of the variable.", + "allOf": [ + { + "$ref": "#/definitions/entity.VariableType" + } + ] + }, + "value": { + "description": "Value is the value of the variable.", + "type": "string" + }, + "variableSet": { + "description": "VariableSet is the variable set to which the variable belongs.", + "type": "string" + } + } + }, + "request.CreateVariableSetRequest": { + "type": "object", + "required": [ + "labels", + "name" + ], + "properties": { + "labels": { + "description": "Labels clarifies the scope of the variable set.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name is the name of the variable set.", + "type": "string" + } + } + }, "request.CreateWorkspaceRequest": { "type": "object", "required": [ @@ -5652,6 +6542,55 @@ const docTemplate = `{ } } }, + "request.UpdateVariableRequest": { + "type": "object", + "required": [ + "name", + "variableSet" + ], + "properties": { + "name": { + "description": "Name is the name of the variable.", + "type": "string" + }, + "type": { + "description": "Type is the type of the variable.", + "allOf": [ + { + "$ref": "#/definitions/entity.VariableType" + } + ] + }, + "value": { + "description": "Value is the value of the variable.", + "type": "string" + }, + "variableSet": { + "description": "VariableSet is the variable set to which the variable belongs.", + "type": "string" + } + } + }, + "request.UpdateVariableSetRequest": { + "type": "object", + "required": [ + "labels", + "name" + ], + "properties": { + "labels": { + "description": "Labels clarifies the scope of the variable set.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name is the name of the variable set.", + "type": "string" + } + } + }, "request.UpdateWorkspaceRequest": { "type": "object", "required": [ @@ -5881,6 +6820,46 @@ const docTemplate = `{ } } }, + "response.PaginatedVariableResponse": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "variable": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.Variable" + } + } + } + }, + "response.PaginatedVariableSetResponse": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "variableSets": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + }, "response.PaginatedWorkspaceResponse": { "type": "object", "properties": { diff --git a/api/openapispec/swagger.json b/api/openapispec/swagger.json index 3e4e77c91..f18bf2eb0 100644 --- a/api/openapispec/swagger.json +++ b/api/openapispec/swagger.json @@ -3336,6 +3336,795 @@ } } }, + "/api/v1/variables": { + "get": { + "description": "List variable information", + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "List variables", + "operationId": "listVariables", + "parameters": [ + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "query" + }, + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "query" + }, + { + "type": "integer", + "description": "The current page to fetch. Default to 1", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The size of the page. Default to 10", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "description": "Which field to sort the list by. Default to id", + "name": "sortBy", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to sort the list in ascending order. Default to false", + "name": "ascending", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to list all the variables", + "name": "fetchAll", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PaginatedVariableResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "post": { + "description": "Create a new variable", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Create variable", + "operationId": "createVariable", + "parameters": [ + { + "description": "Created variable", + "name": "variable", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateVariableRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Variable" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variables/{variableSetName}/{variableName}": { + "get": { + "description": "Get variable information by variable name and the variable set it belongs", + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Get variable", + "operationId": "getVariable", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Variable" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "put": { + "description": "Update the specified variable with name and the variable set it belongs to", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Update variable", + "operationId": "updateVariable", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "path", + "required": true + }, + { + "description": "Updated variable", + "name": "variable", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateVariableRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Variable" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "delete": { + "description": "Delete the specified variable by name and the variable set it belongs to", + "produces": [ + "application/json" + ], + "tags": [ + "variable" + ], + "summary": "Delete variable", + "operationId": "deleteVariable", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Variable Name", + "name": "variableName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variablesets": { + "get": { + "description": "List variable set information", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "List variable sets", + "operationId": "listVariableSets", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "query" + }, + { + "type": "integer", + "description": "The current page to fetch. Default to 1", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The size of the page. Default to 10", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "description": "Which field to sort the list by. Default to id", + "name": "sortBy", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to sort the list in ascending order. Default to false", + "name": "ascending", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to list all the variable sets", + "name": "fetchAll", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PaginatedVariableSetResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "post": { + "description": "Create a new variable set", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Create variable set", + "operationId": "createVariableSet", + "parameters": [ + { + "description": "Created variable set", + "name": "variableSet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateVariableSetRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variablesets/matched": { + "get": { + "description": "List variable set information by label selectors", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "List variable sets by labels", + "operationId": "listVariableSetsByLabels", + "parameters": [ + { + "type": "string", + "description": "Label selectors to match variable sets", + "name": "selector", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PaginatedVariableSetResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, + "/api/v1/variablesets/{variableSetName}": { + "get": { + "description": "Get variable set information by variable set name", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Get variable set", + "operationId": "getVariableSet", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "put": { + "description": "Update the specified variable set with name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Update variable set", + "operationId": "updateVariableSet", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + }, + { + "description": "Updated variable set", + "name": "variableSet", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateVariableSetRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + }, + "delete": { + "description": "Delete the specified variable set by name", + "produces": [ + "application/json" + ], + "tags": [ + "variable_set" + ], + "summary": "Delete variable set", + "operationId": "deleteVariableSet", + "parameters": [ + { + "type": "string", + "description": "Variable Set Name", + "name": "variableSetName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "401": { + "description": "Unauthorized", + "schema": {} + }, + "404": { + "description": "Not Found", + "schema": {} + }, + "429": { + "description": "Too Many Requests", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, "/api/v1/workspaces": { "get": { "description": "List all workspaces", @@ -3963,18 +4752,18 @@ "constant.SourceProviderType": { "type": "string", "enum": [ + "git", "git", "github", "oci", - "local", - "git" + "local" ], "x-enum-varnames": [ + "DefaultSourceType", "SourceProviderTypeGit", "SourceProviderTypeGithub", "SourceProviderTypeOCI", - "SourceProviderTypeLocal", - "DefaultSourceType" + "SourceProviderTypeLocal" ] }, "constant.StackState": { @@ -4594,6 +5383,58 @@ } } }, + "entity.Variable": { + "type": "object", + "properties": { + "name": { + "description": "Name is the name of the variable.", + "type": "string" + }, + "type": { + "description": "Type is the text type of the variable.", + "allOf": [ + { + "$ref": "#/definitions/entity.VariableType" + } + ] + }, + "value": { + "description": "Value is the value of the variable.", + "type": "string" + }, + "variableSet": { + "description": "VariableSet is the variable set to which the variable belongs.", + "type": "string" + } + } + }, + "entity.VariableSet": { + "type": "object", + "properties": { + "labels": { + "description": "Labels clarifies the scope of the variable set.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name is the name of the variable set.", + "type": "string" + } + } + }, + "entity.VariableType": { + "type": "string", + "enum": [ + "PlainText", + "CipherText" + ], + "x-enum-varnames": [ + "PlainTextType", + "CipherTextType" + ] + }, "entity.Workspace": { "type": "object", "properties": { @@ -5355,6 +6196,55 @@ } } }, + "request.CreateVariableRequest": { + "type": "object", + "required": [ + "name", + "variableSet" + ], + "properties": { + "name": { + "description": "Name is the name of the variable.", + "type": "string" + }, + "type": { + "description": "Type is the type of the variable.", + "allOf": [ + { + "$ref": "#/definitions/entity.VariableType" + } + ] + }, + "value": { + "description": "Value is the value of the variable.", + "type": "string" + }, + "variableSet": { + "description": "VariableSet is the variable set to which the variable belongs.", + "type": "string" + } + } + }, + "request.CreateVariableSetRequest": { + "type": "object", + "required": [ + "labels", + "name" + ], + "properties": { + "labels": { + "description": "Labels clarifies the scope of the variable set.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name is the name of the variable set.", + "type": "string" + } + } + }, "request.CreateWorkspaceRequest": { "type": "object", "required": [ @@ -5641,6 +6531,55 @@ } } }, + "request.UpdateVariableRequest": { + "type": "object", + "required": [ + "name", + "variableSet" + ], + "properties": { + "name": { + "description": "Name is the name of the variable.", + "type": "string" + }, + "type": { + "description": "Type is the type of the variable.", + "allOf": [ + { + "$ref": "#/definitions/entity.VariableType" + } + ] + }, + "value": { + "description": "Value is the value of the variable.", + "type": "string" + }, + "variableSet": { + "description": "VariableSet is the variable set to which the variable belongs.", + "type": "string" + } + } + }, + "request.UpdateVariableSetRequest": { + "type": "object", + "required": [ + "labels", + "name" + ], + "properties": { + "labels": { + "description": "Labels clarifies the scope of the variable set.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name is the name of the variable set.", + "type": "string" + } + } + }, "request.UpdateWorkspaceRequest": { "type": "object", "required": [ @@ -5870,6 +6809,46 @@ } } }, + "response.PaginatedVariableResponse": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "variable": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.Variable" + } + } + } + }, + "response.PaginatedVariableSetResponse": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "variableSets": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.VariableSet" + } + } + } + }, "response.PaginatedWorkspaceResponse": { "type": "object", "properties": { diff --git a/api/openapispec/swagger.yaml b/api/openapispec/swagger.yaml index 3fd050e18..27b491778 100644 --- a/api/openapispec/swagger.yaml +++ b/api/openapispec/swagger.yaml @@ -30,17 +30,17 @@ definitions: constant.SourceProviderType: enum: - git + - git - github - oci - local - - git type: string x-enum-varnames: + - DefaultSourceType - SourceProviderTypeGit - SourceProviderTypeGithub - SourceProviderTypeOCI - SourceProviderTypeLocal - - DefaultSourceType constant.StackState: enum: - UnSynced @@ -475,6 +475,41 @@ definitions: description: UpdateTimestamp is the timestamp of the updated for the stack. type: string type: object + entity.Variable: + properties: + name: + description: Name is the name of the variable. + type: string + type: + allOf: + - $ref: '#/definitions/entity.VariableType' + description: Type is the text type of the variable. + value: + description: Value is the value of the variable. + type: string + variableSet: + description: VariableSet is the variable set to which the variable belongs. + type: string + type: object + entity.VariableSet: + properties: + labels: + additionalProperties: + type: string + description: Labels clarifies the scope of the variable set. + type: object + name: + description: Name is the name of the variable set. + type: string + type: object + entity.VariableType: + enum: + - PlainText + - CipherText + type: string + x-enum-varnames: + - PlainTextType + - CipherTextType entity.Workspace: properties: backend: @@ -1015,6 +1050,39 @@ definitions: required: - name type: object + request.CreateVariableRequest: + properties: + name: + description: Name is the name of the variable. + type: string + type: + allOf: + - $ref: '#/definitions/entity.VariableType' + description: Type is the type of the variable. + value: + description: Value is the value of the variable. + type: string + variableSet: + description: VariableSet is the variable set to which the variable belongs. + type: string + required: + - name + - variableSet + type: object + request.CreateVariableSetRequest: + properties: + labels: + additionalProperties: + type: string + description: Labels clarifies the scope of the variable set. + type: object + name: + description: Name is the name of the variable set. + type: string + required: + - labels + - name + type: object request.CreateWorkspaceRequest: properties: backendID: @@ -1219,6 +1287,39 @@ definitions: required: - id type: object + request.UpdateVariableRequest: + properties: + name: + description: Name is the name of the variable. + type: string + type: + allOf: + - $ref: '#/definitions/entity.VariableType' + description: Type is the type of the variable. + value: + description: Value is the value of the variable. + type: string + variableSet: + description: VariableSet is the variable set to which the variable belongs. + type: string + required: + - name + - variableSet + type: object + request.UpdateVariableSetRequest: + properties: + labels: + additionalProperties: + type: string + description: Labels clarifies the scope of the variable set. + type: object + name: + description: Name is the name of the variable set. + type: string + required: + - labels + - name + type: object request.UpdateWorkspaceRequest: properties: description: @@ -1368,6 +1469,32 @@ definitions: total: type: integer type: object + response.PaginatedVariableResponse: + properties: + currentPage: + type: integer + pageSize: + type: integer + total: + type: integer + variable: + items: + $ref: '#/definitions/entity.Variable' + type: array + type: object + response.PaginatedVariableSetResponse: + properties: + currentPage: + type: integer + pageSize: + type: integer + total: + type: integer + variableSets: + items: + $ref: '#/definitions/entity.VariableSet' + type: array + type: object response.PaginatedWorkspaceResponse: properties: currentPage: @@ -3602,6 +3729,522 @@ paths: summary: Asynchronously preview stack tags: - stack + /api/v1/variables: + get: + description: List variable information + operationId: listVariables + parameters: + - description: Variable Name + in: query + name: variableName + type: string + - description: Variable Set Name + in: query + name: variableSetName + type: string + - description: The current page to fetch. Default to 1 + in: query + name: page + type: integer + - description: The size of the page. Default to 10 + in: query + name: pageSize + type: integer + - description: Which field to sort the list by. Default to id + in: query + name: sortBy + type: string + - description: Whether to sort the list in ascending order. Default to false + in: query + name: ascending + type: boolean + - description: Whether to list all the variables + in: query + name: fetchAll + type: boolean + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/response.PaginatedVariableResponse' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: List variables + tags: + - variable + post: + consumes: + - application/json + description: Create a new variable + operationId: createVariable + parameters: + - description: Created variable + in: body + name: variable + required: true + schema: + $ref: '#/definitions/request.CreateVariableRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/entity.Variable' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Create variable + tags: + - variable + /api/v1/variables/{variableSetName}/{variableName}: + delete: + description: Delete the specified variable by name and the variable set it belongs + to + operationId: deleteVariable + parameters: + - description: Variable Set Name + in: path + name: variableSetName + required: true + type: string + - description: Variable Name + in: path + name: variableName + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Delete variable + tags: + - variable + get: + description: Get variable information by variable name and the variable set + it belongs + operationId: getVariable + parameters: + - description: Variable Set Name + in: path + name: variableSetName + required: true + type: string + - description: Variable Name + in: path + name: variableName + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/entity.Variable' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Get variable + tags: + - variable + put: + consumes: + - application/json + description: Update the specified variable with name and the variable set it + belongs to + operationId: updateVariable + parameters: + - description: Variable Set Name + in: path + name: variableSetName + required: true + type: string + - description: Variable Name + in: path + name: variableName + required: true + type: string + - description: Updated variable + in: body + name: variable + required: true + schema: + $ref: '#/definitions/request.UpdateVariableRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/entity.Variable' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Update variable + tags: + - variable + /api/v1/variablesets: + get: + description: List variable set information + operationId: listVariableSets + parameters: + - description: Variable Set Name + in: query + name: variableSetName + type: string + - description: The current page to fetch. Default to 1 + in: query + name: page + type: integer + - description: The size of the page. Default to 10 + in: query + name: pageSize + type: integer + - description: Which field to sort the list by. Default to id + in: query + name: sortBy + type: string + - description: Whether to sort the list in ascending order. Default to false + in: query + name: ascending + type: boolean + - description: Whether to list all the variable sets + in: query + name: fetchAll + type: boolean + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/response.PaginatedVariableSetResponse' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: List variable sets + tags: + - variable_set + post: + consumes: + - application/json + description: Create a new variable set + operationId: createVariableSet + parameters: + - description: Created variable set + in: body + name: variableSet + required: true + schema: + $ref: '#/definitions/request.CreateVariableSetRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/entity.VariableSet' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Create variable set + tags: + - variable_set + /api/v1/variablesets/{variableSetName}: + delete: + description: Delete the specified variable set by name + operationId: deleteVariableSet + parameters: + - description: Variable Set Name + in: path + name: variableSetName + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + type: string + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Delete variable set + tags: + - variable_set + get: + description: Get variable set information by variable set name + operationId: getVariableSet + parameters: + - description: Variable Set Name + in: path + name: variableSetName + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/entity.VariableSet' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Get variable set + tags: + - variable_set + put: + consumes: + - application/json + description: Update the specified variable set with name + operationId: updateVariableSet + parameters: + - description: Variable Set Name + in: path + name: variableSetName + required: true + type: string + - description: Updated variable set + in: body + name: variableSet + required: true + schema: + $ref: '#/definitions/request.UpdateVariableSetRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/entity.VariableSet' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: Update variable set + tags: + - variable_set + /api/v1/variablesets/matched: + get: + description: List variable set information by label selectors + operationId: listVariableSetsByLabels + parameters: + - description: Label selectors to match variable sets + in: query + name: selector + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/handler.Response' + - properties: + data: + $ref: '#/definitions/response.PaginatedVariableSetResponse' + type: object + "400": + description: Bad Request + schema: {} + "401": + description: Unauthorized + schema: {} + "404": + description: Not Found + schema: {} + "429": + description: Too Many Requests + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: List variable sets by labels + tags: + - variable_set /api/v1/workspaces: get: description: List all workspaces diff --git a/pkg/domain/constant/global.go b/pkg/domain/constant/global.go index 2fa6a4dec..7e47bc6b8 100644 --- a/pkg/domain/constant/global.go +++ b/pkg/domain/constant/global.go @@ -27,6 +27,7 @@ const ( ResourcePageSizeLarge = 1000 CommonPageDefault = 1 CommonPageSizeDefault = 10 + CommonMaxResultLimit = 1000 SortByCreateTimestamp = "createTimestamp" SortByModifiedTimestamp = "modifiedTimestamp" SortByName = "name" diff --git a/pkg/domain/constant/variable.go b/pkg/domain/constant/variable.go new file mode 100644 index 000000000..8f4809df5 --- /dev/null +++ b/pkg/domain/constant/variable.go @@ -0,0 +1,9 @@ +package constant + +import "errors" + +var ( + ErrInvalidVariableName = errors.New("variable name can only have alphanumeric characters and underscores with [a-zA-Z0-9_]") + ErrInvalidVariableType = errors.New("invalid variable type, only PlainText and CipherText supported") + ErrEmptyVariableSet = errors.New("variable set should not be empty") +) diff --git a/pkg/domain/constant/variable_set.go b/pkg/domain/constant/variable_set.go new file mode 100644 index 000000000..2f1a98f4d --- /dev/null +++ b/pkg/domain/constant/variable_set.go @@ -0,0 +1,8 @@ +package constant + +import "errors" + +var ( + ErrInvalidVariableSetName = errors.New("variable set name can only have alphanumeric characters and underscores with [a-zA-Z0-9_]") + ErrEmptyVariableSetLabels = errors.New("variable set labels should not be empty") +) diff --git a/pkg/domain/entity/variable.go b/pkg/domain/entity/variable.go new file mode 100644 index 000000000..0a8fe684e --- /dev/null +++ b/pkg/domain/entity/variable.go @@ -0,0 +1,59 @@ +package entity + +import "errors" + +type VariableType string + +const ( + PlainTextType VariableType = "PlainText" + CipherTextType VariableType = "CipherText" +) + +// Variable represents a specific configuration code variable, +// which usually includes the global configuration for Terraform providers like +// api host, access key and secret key. +type Variable struct { + // Name is the name of the variable. + Name string `yaml:"name,omitempty" json:"name,omitempty"` + // Value is the value of the variable. + Value string `yaml:"value,omitempty" json:"value,omitempty"` + // Type is the text type of the variable. + Type VariableType `yaml:"type,omitempty" json:"type,omitempty"` + // VariableSet is the variable set to which the variable belongs. + VariableSet string `yaml:"variableSet,omitempty" json:"variableSet,omitempty"` +} + +// VariableFilter represents the filter conditions to list variables. +type VariableFilter struct { + Name string + VariableSet string + Pagination *Pagination + FetchAll bool +} + +// VariableListResult represents the result of listing variables. +type VariableListResult struct { + Variables []*Variable + Total int +} + +// Validate checks if the variable is valid. +func (v *Variable) Validate() error { + if v == nil { + return errors.New("variable is nil") + } + + if v.Name == "" { + return errors.New("empty variable name") + } + + if v.Type != PlainTextType && v.Type != CipherTextType { + return errors.New("invalid variable type") + } + + if v.VariableSet == "" { + return errors.New("empty variable set name") + } + + return nil +} diff --git a/pkg/domain/entity/variable_set.go b/pkg/domain/entity/variable_set.go new file mode 100644 index 000000000..7bd9b03b5 --- /dev/null +++ b/pkg/domain/entity/variable_set.go @@ -0,0 +1,41 @@ +package entity + +import "errors" + +// VariableSet represents a set of the global configuration variables. +type VariableSet struct { + // Name is the name of the variable set. + Name string `yaml:"name,omitempty" json:"name,omitempty"` + // Labels clarifies the scope of the variable set. + Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` +} + +// VariableSetFilter represents the filter conditions to list variable sets. +type VariableSetFilter struct { + Name string + Pagination *Pagination + FetchAll bool +} + +// VariableSetListResult represents the result of listing variable sets. +type VariableSetListResult struct { + VariableSets []*VariableSet + Total int +} + +// Validate checks if the variable set is valid. +func (vs *VariableSet) Validate() error { + if vs == nil { + return errors.New("variable set is nil") + } + + if vs.Name == "" { + return errors.New("empty variable set name") + } + + if len(vs.Labels) == 0 { + return errors.New("empty variable set labels") + } + + return nil +} diff --git a/pkg/domain/repository/repository.go b/pkg/domain/repository/repository.go index c088b8ca7..7ecc46136 100644 --- a/pkg/domain/repository/repository.go +++ b/pkg/domain/repository/repository.go @@ -152,3 +152,33 @@ type RunRepository interface { // List retrieves all existing run. List(ctx context.Context, filter *entity.RunFilter, sortOptions *entity.SortOptions) (*entity.RunListResult, error) } + +// VariableSetRepository is an interface that defines the repository operations +// for variable sets. It follows the principles of domain-driven design (DDD). +type VariableSetRepository interface { + // Create creates a new variable set. + Create(ctx context.Context, vs *entity.VariableSet) error + // Delete deletes a variable set by its name. + Delete(ctx context.Context, name string) error + // Update updates an existing variable set. + Update(ctx context.Context, vs *entity.VariableSet) error + // Get retrieves a variable set by its name. + Get(ctx context.Context, name string) (*entity.VariableSet, error) + // List retrieves existing variable sets with filter and sort options. + List(ctx context.Context, filter *entity.VariableSetFilter, sortOptions *entity.SortOptions) (*entity.VariableSetListResult, error) +} + +// VariableRepository is an interface that defines the repository operations +// for variables. It follows the principles of domain-driven design (DDD). +type VariableRepository interface { + // Create creates a new variable. + Create(ctx context.Context, v *entity.Variable) error + // Delete deletes a variable by its name and the variable set it belongs to. + Delete(ctx context.Context, name, variableSet string) error + // Update updates an existing variable. + Update(ctx context.Context, v *entity.Variable) error + // Get retrieves a variable by its name and the variable set it belogs to. + Get(ctx context.Context, name, variableSet string) (*entity.Variable, error) + // List retrieves existing variable with filter and sort options. + List(ctx context.Context, filter *entity.VariableFilter, sortOptions *entity.SortOptions) (*entity.VariableListResult, error) +} diff --git a/pkg/domain/request/stack_request.go b/pkg/domain/request/stack_request.go index 02103cf0b..e7b51b28b 100644 --- a/pkg/domain/request/stack_request.go +++ b/pkg/domain/request/stack_request.go @@ -4,7 +4,6 @@ import ( "net/http" "kusionstack.io/kusion/pkg/domain/constant" - "kusionstack.io/kusion/pkg/domain/entity" ) // CreateStackRequest represents the create request structure for @@ -30,19 +29,6 @@ type CreateStackRequest struct { Owners []string `json:"owners"` } -type UpdateVariableRequest struct { - // Project is the project related to stack - Project string `json:"project,omitempty"` - // Path is the relative path of the stack within the source. - Path string `json:"path,omitempty"` - IsSecret bool `json:"isSecret,omitempty"` - // key is the unique index to use value in specific stack - Key string `json:"key,omitempty"` - // value is the plain value of no sensitive data - Value string `json:"value,omitempty"` - SecretValue *entity.SecretValue `json:"secretValue,omitempty"` -} - // UpdateStackRequest represents the update request structure for // stack. type UpdateStackRequest struct { @@ -76,10 +62,6 @@ func (payload *UpdateStackRequest) Decode(r *http.Request) error { return decode(r, payload) } -func (payload *UpdateVariableRequest) Decode(r *http.Request) error { - return decode(r, payload) -} - func (payload *CreateStackRequest) Validate() error { if payload.ProjectID == 0 && payload.ProjectName == "" { return constant.ErrProjectNameOrIDRequired diff --git a/pkg/domain/request/variable_request.go b/pkg/domain/request/variable_request.go new file mode 100644 index 000000000..a1876c133 --- /dev/null +++ b/pkg/domain/request/variable_request.go @@ -0,0 +1,82 @@ +package request + +import ( + "net/http" + + "kusionstack.io/kusion/pkg/domain/constant" + "kusionstack.io/kusion/pkg/domain/entity" +) + +// CreateVariableRequest represents the create request structure +// for a variable. +type CreateVariableRequest struct { + // Name is the name of the variable. + Name string `json:"name" binding:"required"` + // Value is the value of the variable. + Value string `json:"value"` + // Type is the type of the variable. + Type entity.VariableType `json:"type"` + // VariableSet is the variable set to which the variable belongs. + VariableSet string `json:"variableSet" binding:"required"` +} + +// UpdateVariableRequest represents the update request structure +// for a variable. +type UpdateVariableRequest struct { + // Name is the name of the variable. + Name string `json:"name" binding:"required"` + // Value is the value of the variable. + Value string `json:"value"` + // Type is the type of the variable. + Type entity.VariableType `json:"type"` + // VariableSet is the variable set to which the variable belongs. + VariableSet string `json:"variableSet" binding:"required"` +} + +func (payload *CreateVariableRequest) Validate() error { + // Validate variable name. + if validName(payload.Name) { + return constant.ErrInvalidVariableName + } + + // Validate variable set name. . + if validName(payload.VariableSet) { + return constant.ErrInvalidVariableSetName + } + + // Validate variable type. + if payload.Type != "" && + payload.Type != entity.PlainTextType && payload.Type != entity.CipherTextType { + return constant.ErrInvalidVariableType + } + + return nil +} + +func (payload *UpdateVariableRequest) Validate() error { + // Validate variable name. + if validName(payload.Name) { + return constant.ErrInvalidVariableName + } + + // Validate variable set name. . + if validName(payload.VariableSet) { + return constant.ErrInvalidVariableSetName + } + + // Validate variable type. + if payload.Type != "" && + payload.Type != entity.PlainTextType && payload.Type != entity.CipherTextType { + return constant.ErrInvalidVariableType + } + + return nil +} + +func (payload *CreateVariableRequest) Decode(r *http.Request) error { + return decode(r, payload) +} + +func (payload *UpdateVariableRequest) Decode(r *http.Request) error { + return decode(r, payload) +} diff --git a/pkg/domain/request/variable_set_request.go b/pkg/domain/request/variable_set_request.go new file mode 100644 index 000000000..3ea2e1a25 --- /dev/null +++ b/pkg/domain/request/variable_set_request.go @@ -0,0 +1,59 @@ +package request + +import ( + "net/http" + + "kusionstack.io/kusion/pkg/domain/constant" +) + +// CreateVariableSetRequest represents the create request structure +// for a variable set. +type CreateVariableSetRequest struct { + // Name is the name of the variable set. + Name string `json:"name" binding:"required"` + // Labels clarifies the scope of the variable set. + Labels map[string]string `json:"labels" binding:"required"` +} + +// UpdateVariableSetRequest represents the update request structure +// for a variable set. +type UpdateVariableSetRequest struct { + // Name is the name of the variable set. + Name string `json:"name" binding:"required"` + // Labels clarifies the scope of the variable set. + Labels map[string]string `json:"labels" binding:"required"` +} + +func (payload *CreateVariableSetRequest) Validate() error { + // Validate variable set name. + if validName(payload.Name) { + return constant.ErrInvalidVariableSetName + } + + if len(payload.Labels) == 0 { + return constant.ErrEmptyVariableSetLabels + } + + return nil +} + +func (payload *UpdateVariableSetRequest) Validate() error { + // Validate variable set name. + if payload.Name != "" && validName(payload.Name) { + return constant.ErrInvalidVariableSetName + } + + if len(payload.Labels) == 0 { + return constant.ErrEmptyVariableSetLabels + } + + return nil +} + +func (payload *CreateVariableSetRequest) Decode(r *http.Request) error { + return decode(r, payload) +} + +func (payload *UpdateVariableSetRequest) Decode(r *http.Request) error { + return decode(r, payload) +} diff --git a/pkg/domain/response/variable_response.go b/pkg/domain/response/variable_response.go new file mode 100644 index 000000000..645176b85 --- /dev/null +++ b/pkg/domain/response/variable_response.go @@ -0,0 +1,10 @@ +package response + +import "kusionstack.io/kusion/pkg/domain/entity" + +type PaginatedVariableResponse struct { + Variables []*entity.Variable `json:"variable"` + Total int `json:"total"` + CurrentPage int `json:"currentPage"` + PageSize int `json:"pageSize"` +} diff --git a/pkg/domain/response/variable_set_response.go b/pkg/domain/response/variable_set_response.go new file mode 100644 index 000000000..d465cdb07 --- /dev/null +++ b/pkg/domain/response/variable_set_response.go @@ -0,0 +1,15 @@ +package response + +import "kusionstack.io/kusion/pkg/domain/entity" + +type PaginatedVariableSetResponse struct { + VariableSets []*entity.VariableSet `json:"variableSets"` + Total int `json:"total"` + CurrentPage int `json:"currentPage"` + PageSize int `json:"pageSize"` +} + +type SelectedVariableSetResponse struct { + VariableSets []*entity.VariableSet `json:"variableSets"` + Total int `json:"total"` +} diff --git a/pkg/infra/persistence/types.go b/pkg/infra/persistence/types.go index 356f45b1d..5d86e7882 100644 --- a/pkg/infra/persistence/types.go +++ b/pkg/infra/persistence/types.go @@ -26,6 +26,8 @@ var ( ErrResourceModelNil = errors.New("resource model can't be nil") ErrFailedToGetModuleDocRemote = errors.New("failed to parse module doc remote") ErrRunModelNil = errors.New("run model can't be nil") + ErrVariableSetModelNil = errors.New("variable set model can't be nil") + ErrVariableModelNil = errors.New("variable model can't be nil") ErrFailedToGetRunType = errors.New("failed to parse run type") ErrFailedToGetRunStatus = errors.New("failed to parse run status") ) diff --git a/pkg/infra/persistence/util.go b/pkg/infra/persistence/util.go index 7c67dec6e..8d1af09c4 100644 --- a/pkg/infra/persistence/util.go +++ b/pkg/infra/persistence/util.go @@ -231,6 +231,30 @@ func GetModuleQuery(filter *entity.ModuleFilter) (string, []interface{}) { return CombineQueryParts(pattern), args } +func GetVariableSetQuery(filter *entity.VariableSetFilter) (string, []interface{}) { + pattern := make([]string, 0) + args := make([]interface{}, 0) + if filter.Name != "" { + pattern = append(pattern, "variable_set.name = ?") + args = append(args, filter.Name) + } + return CombineQueryParts(pattern), args +} + +func GetVariableQuery(filter *entity.VariableFilter) (string, []interface{}) { + pattern := make([]string, 0) + args := make([]interface{}, 0) + if filter.Name != "" { + pattern = append(pattern, "variable.name = ?") + args = append(args, filter.Name) + } + if filter.VariableSet != "" { + pattern = append(pattern, "variable.variable_set LIKE ?") + args = append(args, filter.VariableSet) + } + return CombineQueryParts(pattern), args +} + func CombineQueryParts(queryParts []string) string { queryString := "" if len(queryParts) > 0 { diff --git a/pkg/infra/persistence/variable.go b/pkg/infra/persistence/variable.go new file mode 100644 index 000000000..d808f7a3d --- /dev/null +++ b/pkg/infra/persistence/variable.go @@ -0,0 +1,147 @@ +package persistence + +import ( + "context" + + "gorm.io/gorm" + "kusionstack.io/kusion/pkg/domain/constant" + "kusionstack.io/kusion/pkg/domain/entity" + "kusionstack.io/kusion/pkg/domain/repository" +) + +// The variableRepository type implements the repository.VariableRepository interface. +// If the variableRepository type does not implement all the methods of the interface, +// the compiler will produce an error. +var _ repository.VariableRepository = &variableRepository{} + +// variableRepository is a repository that stores variables in a gorm database. +type variableRepository struct { + // db is the underlying gorm database where variables are stored. + db *gorm.DB +} + +// NewVariableRepository creates a new variable repository. +func NewVariableRepository(db *gorm.DB) repository.VariableRepository { + return &variableRepository{db: db} +} + +// Create saves a variable to the repository. +func (v *variableRepository) Create(ctx context.Context, dataEntity *entity.Variable) error { + v.db.AutoMigrate(&VariableModel{}) + if err := dataEntity.Validate(); err != nil { + return err + } + + // Map the data from entity to Do. + var dataModel VariableModel + if err := dataModel.FromEntity(dataEntity); err != nil { + return err + } + + return v.db.Transaction(func(tx *gorm.DB) error { + // Create new record in the storage. + if err := tx.WithContext(ctx).Create(&dataModel).Error; err != nil { + return err + } + + // Map fresh record's data into Entity. + newEntity, err := dataModel.ToEntity() + if err != nil { + return err + } + *dataEntity = *newEntity + + return nil + }) +} + +// Delete removes a variable from the repository. +func (v *variableRepository) Delete(ctx context.Context, name, variableSet string) error { + return v.db.Transaction(func(tx *gorm.DB) error { + var dataModel VariableModel + if err := tx.WithContext(ctx). + Where("name = ?", name).Where("variable_set = ?", variableSet).First(&dataModel).Error; err != nil { + return err + } + + return tx.WithContext(ctx).Unscoped().Delete(&dataModel).Error + }) +} + +// Update updates an existing variable in the repository. +func (v *variableRepository) Update(ctx context.Context, dataEntity *entity.Variable) error { + // Map the data from Entity to DO. + var dataModel VariableModel + if err := dataModel.FromEntity(dataEntity); err != nil { + return err + } + + if err := v.db.WithContext(ctx). + Where("name = ?", dataModel.Name).Where("variable_set = ?", dataModel.VariableSet).Updates(&dataModel).Error; err != nil { + return err + } + + return nil +} + +// Get retrieves a variable by its name and the variable set it belongs to. +func (v *variableRepository) Get(ctx context.Context, name, variableSet string) (*entity.Variable, error) { + var dataModel VariableModel + if err := v.db.WithContext(ctx). + Where("name = ?", name).Where("variable_set = ?", variableSet).First(&dataModel).Error; err != nil { + return nil, err + } + + return dataModel.ToEntity() +} + +// List retrieves existing variables with filter and sort options. +func (v *variableRepository) List(ctx context.Context, + filter *entity.VariableFilter, sortOptions *entity.SortOptions, +) (*entity.VariableListResult, error) { + var dataModel []VariableModel + variableEntityList := make([]*entity.Variable, 0) + pattern, args := GetVariableQuery(filter) + + sortArgs := sortOptions.Field + if !sortOptions.Ascending { + sortArgs += " DESC" + } + + searchResult := v.db.WithContext(ctx).Order(sortArgs).Where(pattern, args...) + + // Get total rows. + var totalRows int64 + searchResult.Model(dataModel).Count(&totalRows) + + // Set the page size as the maximum count limit of responses, + // if `totalRows` is larger than `constant.CommonMaxResultLimit`. + if totalRows > int64(constant.CommonMaxResultLimit) { + filter.Pagination.Page = 1 + filter.Pagination.PageSize = constant.CommonMaxResultLimit + } else if filter.FetchAll { + // Set the page size as the `totalRows` if `filter.FetchAll` sets to true. + filter.Pagination.Page = 1 + filter.Pagination.PageSize = int(totalRows) + } + + // Fetched paginated data from searchResult with offset and limit. + offset := (filter.Pagination.Page - 1) * filter.Pagination.PageSize + result := searchResult.Offset(offset).Limit(filter.Pagination.PageSize).Find(&dataModel) + if result.Error != nil { + return nil, result.Error + } + + for _, variable := range dataModel { + variableEntity, err := variable.ToEntity() + if err != nil { + return nil, err + } + variableEntityList = append(variableEntityList, variableEntity) + } + + return &entity.VariableListResult{ + Variables: variableEntityList, + Total: int(totalRows), + }, nil +} diff --git a/pkg/infra/persistence/variable_model.go b/pkg/infra/persistence/variable_model.go new file mode 100644 index 000000000..dba13c540 --- /dev/null +++ b/pkg/infra/persistence/variable_model.go @@ -0,0 +1,52 @@ +package persistence + +import ( + "gorm.io/gorm" + "kusionstack.io/kusion/pkg/domain/entity" +) + +// VariableModel is a DO used to map the entity to the database. +type VariableModel struct { + gorm.Model + // Name is the name of the variable. + Name string `gorm:"index:unique_variable,unique"` + // Value is the value of the variable. + Value string + // Type is the text type of the variable. + Type entity.VariableType + // VariableSet is the variable set to which the variable belongs. + VariableSet string `gorm:"index:unique_variable,unique"` +} + +// The TableName method returns the name of the database table that the struct is mapped to. +func (v *VariableModel) TableName() string { + return "variable" +} + +// ToEntity converts the DO to an entity. +func (v *VariableModel) ToEntity() (*entity.Variable, error) { + if v == nil { + return nil, ErrVariableModelNil + } + + return &entity.Variable{ + Name: v.Name, + Value: v.Value, + Type: v.Type, + VariableSet: v.VariableSet, + }, nil +} + +// FromEntity converts an entity to a DO. +func (v *VariableModel) FromEntity(e *entity.Variable) error { + if v == nil { + return ErrVariableModelNil + } + + v.Name = e.Name + v.Value = e.Value + v.Type = e.Type + v.VariableSet = e.VariableSet + + return nil +} diff --git a/pkg/infra/persistence/variable_set.go b/pkg/infra/persistence/variable_set.go new file mode 100644 index 000000000..4dcd4e011 --- /dev/null +++ b/pkg/infra/persistence/variable_set.go @@ -0,0 +1,146 @@ +package persistence + +import ( + "context" + + "gorm.io/gorm" + "kusionstack.io/kusion/pkg/domain/constant" + "kusionstack.io/kusion/pkg/domain/entity" + "kusionstack.io/kusion/pkg/domain/repository" +) + +// The variableSetRepository type implements the repository.VariableSetRepository interface. +// If the variableSetRepository type does not implement all the methods of the interface, +// the compiler will produce an error. +var _ repository.VariableSetRepository = &variableSetRepository{} + +// variableSetRepository is a repository that stores variable sets in a gorm database. +type variableSetRepository struct { + // db is the underlying gorm database where variable sets are stored. + db *gorm.DB +} + +// NewVariableSetRepository creates a new variableSet repository. +func NewVariableSetRepository(db *gorm.DB) repository.VariableSetRepository { + return &variableSetRepository{db: db} +} + +// Create saves a variable set to the repository. +func (vs *variableSetRepository) Create(ctx context.Context, dataEntity *entity.VariableSet) error { + vs.db.AutoMigrate(&VariableSetModel{}) + if err := dataEntity.Validate(); err != nil { + return err + } + + // Map the data from entity to DO. + var dataModel VariableSetModel + if err := dataModel.FromEntity(dataEntity); err != nil { + return err + } + + return vs.db.Transaction(func(tx *gorm.DB) error { + // Create new record in the storage. + if err := tx.WithContext(ctx).Create(&dataModel).Error; err != nil { + return err + } + + // Map fresh record's data into Entity. + newEntity, err := dataModel.ToEntity() + if err != nil { + return err + } + *dataEntity = *newEntity + + return nil + }) +} + +// Delete removes a variable set from the repository. +func (vs *variableSetRepository) Delete(ctx context.Context, name string) error { + return vs.db.Transaction(func(tx *gorm.DB) error { + var dataModel VariableSetModel + if err := tx.WithContext(ctx). + Where("name = ?", name).First(&dataModel).Error; err != nil { + return err + } + + return tx.WithContext(ctx).Unscoped().Delete(&dataModel).Error + }) +} + +// Update updates an existing variable set in the repository. +func (vs *variableSetRepository) Update(ctx context.Context, dataEntity *entity.VariableSet) error { + // Map the data from Entity to DO. + var dataModel VariableSetModel + if err := dataModel.FromEntity(dataEntity); err != nil { + return err + } + + if err := vs.db.WithContext(ctx). + Where("name = ?", dataModel.Name).Updates(&dataModel).Error; err != nil { + return err + } + + return nil +} + +// Get retrieves a variable set by its name. +func (vs *variableSetRepository) Get(ctx context.Context, name string) (*entity.VariableSet, error) { + var dataModel VariableSetModel + if err := vs.db.WithContext(ctx).Where("name = ?", name).First(&dataModel).Error; err != nil { + return nil, err + } + + return dataModel.ToEntity() +} + +// List retrieves existing variable sets with filter and sort options. +func (vs *variableSetRepository) List(ctx context.Context, + filter *entity.VariableSetFilter, sortOptions *entity.SortOptions, +) (*entity.VariableSetListResult, error) { + var dataModel []VariableSetModel + variableSetEntityList := make([]*entity.VariableSet, 0) + pattern, args := GetVariableSetQuery(filter) + + sortArgs := sortOptions.Field + if !sortOptions.Ascending { + sortArgs += " DESC" + } + + searchResult := vs.db.WithContext(ctx).Order(sortArgs).Where(pattern, args...) + + // Get total rows. + var totalRows int64 + searchResult.Model(dataModel).Count(&totalRows) + + // Set the page size as the maximum count limit of responses, + // if `totalRows` is larger than `constant.CommonMaxResultLimit`. + if totalRows > int64(constant.CommonMaxResultLimit) { + filter.Pagination.Page = 1 + filter.Pagination.PageSize = constant.CommonMaxResultLimit + } else if filter.FetchAll { + // Set the page size as the `totalRows` if `filter.FetchAll` sets to true. + filter.Pagination.Page = 1 + filter.Pagination.PageSize = int(totalRows) + } + + // Fetched paginated data from searchResult with offset and limit. + offset := (filter.Pagination.Page - 1) * filter.Pagination.PageSize + result := searchResult.Offset(offset).Limit(filter.Pagination.PageSize).Find(&dataModel) + if result.Error != nil { + return nil, result.Error + } + + for _, variableSet := range dataModel { + variableSetEntity, err := variableSet.ToEntity() + if err != nil { + return nil, err + } + variableSetEntityList = append(variableSetEntityList, variableSetEntity) + } + + return &entity.VariableSetListResult{ + VariableSets: variableSetEntityList, + Total: int(totalRows), + }, nil +} diff --git a/pkg/infra/persistence/variable_set_model.go b/pkg/infra/persistence/variable_set_model.go new file mode 100644 index 000000000..6bbc2f20d --- /dev/null +++ b/pkg/infra/persistence/variable_set_model.go @@ -0,0 +1,44 @@ +package persistence + +import ( + "gorm.io/gorm" + "kusionstack.io/kusion/pkg/domain/entity" +) + +// VariableSetModel is a DO used to map the entity to the database. +type VariableSetModel struct { + gorm.Model + // Name is the name of the variable set. + Name string `gorm:"index:unique_variable_set,unique"` + // Labels clarifies the scope of the variable set. + Labels map[string]string `gorm:"serializer:json" json:"labels"` +} + +// The TableName method returns the name of the database table that the struct is mapped to. +func (vs *VariableSetModel) TableName() string { + return "variable_set" +} + +// ToEntity converts the DO to an entity. +func (vs *VariableSetModel) ToEntity() (*entity.VariableSet, error) { + if vs == nil { + return nil, ErrVariableSetModelNil + } + + return &entity.VariableSet{ + Name: vs.Name, + Labels: vs.Labels, + }, nil +} + +// FromEntity converts an entity to a DO. +func (vs *VariableSetModel) FromEntity(e *entity.VariableSet) error { + if vs == nil { + return ErrVariableSetModelNil + } + + vs.Name = e.Name + vs.Labels = e.Labels + + return nil +} diff --git a/pkg/server/handler/variable/handler.go b/pkg/server/handler/variable/handler.go new file mode 100644 index 000000000..e508aa01b --- /dev/null +++ b/pkg/server/handler/variable/handler.go @@ -0,0 +1,247 @@ +package variable + +import ( + "context" + "errors" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/httplog/v2" + "github.com/go-chi/render" + "kusionstack.io/kusion/pkg/domain/request" + "kusionstack.io/kusion/pkg/domain/response" + "kusionstack.io/kusion/pkg/server/handler" + "kusionstack.io/kusion/pkg/server/manager/variable" + "kusionstack.io/kusion/pkg/server/manager/variableset" + logutil "kusionstack.io/kusion/pkg/server/util/logging" +) + +// @Id createVariable +// @Summary Create variable +// @Description Create a new variable +// @Tags variable +// @Accept json +// @Produce json +// @Param variable body request.CreateVariableRequest true "Created variable" +// @Success 200 {object} handler.Response{data=entity.Variable} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variables [post] +func (h *Handler) CreateVariable() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx := r.Context() + logger := logutil.GetLogger(ctx) + logger.Info("Creating variable...") + + // Decode the request body into the payload. + var requestPayload request.CreateVariableRequest + if err := requestPayload.Decode(r); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Validate request payload. + if err := requestPayload.Validate(); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Return the created entity. + createdEntity, err := h.variableManager.CreateVariable(ctx, requestPayload) + handler.HandleResult(w, r, ctx, err, createdEntity) + } +} + +// @Id deleteVariable +// @Summary Delete variable +// @Description Delete the specified variable by name and the variable set it belongs to +// @Tags variable +// @Produce json +// @Param variableSetName path string true "Variable Set Name" +// @Param variableName path string true "Variable Name" +// @Success 200 {object} handler.Response{data=string} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variables/{variableSetName}/{variableName} [delete] +func (h *Handler) DeleteVariable() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx, logger, params, err := requestHelper(r) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + logger.Info("Deleting variable...") + + err = h.variableManager.DeleteVariableByNameAndVariableSet(ctx, params.VariableName, params.VariableSetName) + handler.HandleResult(w, r, ctx, err, "Deletion Success") + } +} + +// @Id updateVariable +// @Summary Update variable +// @Description Update the specified variable with name and the variable set it belongs to +// @Tags variable +// @Accept json +// @Produce json +// @Param variableSetName path string true "Variable Set Name" +// @Param variableName path string true "Variable Name" +// @Param variable body request.UpdateVariableRequest true "Updated variable" +// @Success 200 {object} handler.Response{data=entity.Variable} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variables/{variableSetName}/{variableName} [put] +func (h *Handler) UpdateVariable() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx, logger, params, err := requestHelper(r) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + logger.Info("Updating variable...") + + // Decode the request body into the payload. + var requestPayload request.UpdateVariableRequest + if err := requestPayload.Decode(r); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Validate request payload. + if requestPayload.Name != "" && requestPayload.Name != params.VariableName { + render.Render(w, r, handler. + FailureResponse(ctx, errors.New("inconsistent variable name in path and request body"))) + return + } + if requestPayload.VariableSet != "" && requestPayload.VariableSet != params.VariableSetName { + render.Render(w, r, handler. + FailureResponse(ctx, errors.New("inconsistent variable set name in path and request body"))) + return + } + if err := requestPayload.Validate(); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Return the updated variable. + updatedEntity, err := h.variableManager. + UpdateVariableByNameAndVariableSet(ctx, params.VariableName, params.VariableSetName, requestPayload) + handler.HandleResult(w, r, ctx, err, updatedEntity) + } +} + +// @Id getVariable +// @Summary Get variable +// @Description Get variable information by variable name and the variable set it belongs +// @Tags variable +// @Produce json +// @Param variableSetName path string true "Variable Set Name" +// @Param variableName path string true "Variable Name" +// @Success 200 {object} handler.Response{data=entity.Variable} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variables/{variableSetName}/{variableName} [get] +func (h *Handler) GetVariable() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx, logger, params, err := requestHelper(r) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + logger.Info("Getting variable...") + + existingEntity, err := h.variableManager.GetVariableByNameAndVariableSet(ctx, + params.VariableName, params.VariableSetName) + handler.HandleResult(w, r, ctx, err, existingEntity) + } +} + +// @Id listVariables +// @Summary List variables +// @Description List variable information +// @Tags variable +// @Produce json +// @Param variableName query string false "Variable Name" +// @Param variableSetName query string false "Variable Set Name" +// @Param page query uint false "The current page to fetch. Default to 1" +// @Param pageSize query uint false "The size of the page. Default to 10" +// @Param sortBy query string false "Which field to sort the list by. Default to id" +// @Param ascending query bool false "Whether to sort the list in ascending order. Default to false" +// @Param fetchAll query bool false "Whether to list all the variables" +// @Success 200 {object} handler.Response{data=response.PaginatedVariableResponse} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variables [get] +func (h *Handler) ListVariables() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx := r.Context() + logger := logutil.GetLogger(ctx) + logger.Info("Listing variables...") + + query := r.URL.Query() + + // Get variable filter. + filter, variableSortOptions, err := h.variableManager.BuildVariableFilterAndSortOptions(ctx, &query) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // List variables with pagination. + variableEntities, err := h.variableManager.ListVariables(ctx, filter, variableSortOptions) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + paginatedResponse := response.PaginatedVariableResponse{ + Variables: variableEntities.Variables, + Total: variableEntities.Total, + CurrentPage: filter.Pagination.Page, + PageSize: filter.Pagination.PageSize, + } + handler.HandleResult(w, r, ctx, err, paginatedResponse) + } +} + +func requestHelper(r *http.Request) (context.Context, *httplog.Logger, *VariableRequestParams, error) { + ctx := r.Context() + logger := logutil.GetLogger(ctx) + + // Get URL parameters. + variableSetName := chi.URLParam(r, "variableSetName") + if variableSetName == "" { + return nil, nil, nil, variableset.ErrEmptyVariableSetName + } + + variableName := chi.URLParam(r, "variableName") + if variableName == "" { + return nil, nil, nil, variable.ErrEmptyVariableName + } + + params := VariableRequestParams{ + VariableSetName: variableSetName, + VariableName: variableName, + } + + return ctx, logger, ¶ms, nil +} diff --git a/pkg/server/handler/variable/types.go b/pkg/server/handler/variable/types.go new file mode 100644 index 000000000..9fded7aae --- /dev/null +++ b/pkg/server/handler/variable/types.go @@ -0,0 +1,20 @@ +package variable + +import "kusionstack.io/kusion/pkg/server/manager/variable" + +type Handler struct { + variableManager *variable.VariableManager +} + +func NewHandler( + variableManager *variable.VariableManager, +) (*Handler, error) { + return &Handler{ + variableManager: variableManager, + }, nil +} + +type VariableRequestParams struct { + VariableSetName string + VariableName string +} diff --git a/pkg/server/handler/variableset/handler.go b/pkg/server/handler/variableset/handler.go new file mode 100644 index 000000000..94cd572ab --- /dev/null +++ b/pkg/server/handler/variableset/handler.go @@ -0,0 +1,321 @@ +package variableset + +import ( + "context" + "errors" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/httplog/v2" + "github.com/go-chi/render" + "k8s.io/apimachinery/pkg/labels" + "kusionstack.io/kusion/pkg/domain/constant" + "kusionstack.io/kusion/pkg/domain/entity" + "kusionstack.io/kusion/pkg/domain/request" + "kusionstack.io/kusion/pkg/domain/response" + "kusionstack.io/kusion/pkg/server/handler" + "kusionstack.io/kusion/pkg/server/manager/variableset" + logutil "kusionstack.io/kusion/pkg/server/util/logging" +) + +// @Id createVariableSet +// @Summary Create variable set +// @Description Create a new variable set +// @Tags variable_set +// @Accept json +// @Produce json +// @Param variableSet body request.CreateVariableSetRequest true "Created variable set" +// @Success 200 {object} handler.Response{data=entity.VariableSet} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variablesets [post] +func (h *Handler) CreateVariableSet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx := r.Context() + logger := logutil.GetLogger(ctx) + logger.Info("Creating variable set...") + + // Decode the request body into the payload. + var requestPayload request.CreateVariableSetRequest + if err := requestPayload.Decode(r); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Validate request payload. + if err := requestPayload.Validate(); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Return the created entity. + createdEntity, err := h.variableSetManager.CreateVariableSet(ctx, requestPayload) + handler.HandleResult(w, r, ctx, err, createdEntity) + } +} + +// @Id deleteVariableSet +// @Summary Delete variable set +// @Description Delete the specified variable set by name +// @Tags variable_set +// @Produce json +// @Param variableSetName path string true "Variable Set Name" +// @Success 200 {object} handler.Response{data=string} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variablesets/{variableSetName} [delete] +func (h *Handler) DeleteVariableSet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx, logger, params, err := requestHelper(r) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + logger.Info("Deleting variable set...") + + err = h.variableSetManager.DeleteVariableSetByName(ctx, params.VariableSetName) + handler.HandleResult(w, r, ctx, err, "Deletion Success") + } +} + +// @Id updateVariableSet +// @Summary Update variable set +// @Description Update the specified variable set with name +// @Tags variable_set +// @Accept json +// @Produce json +// @Param variableSetName path string true "Variable Set Name" +// @Param variableSet body request.UpdateVariableSetRequest true "Updated variable set" +// @Success 200 {object} handler.Response{data=entity.VariableSet} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variablesets/{variableSetName} [put] +func (h *Handler) UpdateVariableSet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx, logger, params, err := requestHelper(r) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + logger.Info("Updating variable set...") + + // Decode the request body into the payload. + var requestPayload request.UpdateVariableSetRequest + if err := requestPayload.Decode(r); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Validate request payload. + if requestPayload.Name != "" && requestPayload.Name != params.VariableSetName { + render.Render(w, r, handler. + FailureResponse(ctx, errors.New("inconsistent variable set name in path and request body"))) + return + } + if err := requestPayload.Validate(); err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // Return the updated variable set. + updatedEntity, err := h.variableSetManager.UpdateVariableSetByName(ctx, params.VariableSetName, requestPayload) + handler.HandleResult(w, r, ctx, err, updatedEntity) + } +} + +// @Id getVariableSet +// @Summary Get variable set +// @Description Get variable set information by variable set name +// @Tags variable_set +// @Produce json +// @Param variableSetName path string true "Variable Set Name" +// @Success 200 {object} handler.Response{data=entity.VariableSet} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variablesets/{variableSetName} [get] +func (h *Handler) GetVariableSet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx, logger, params, err := requestHelper(r) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + logger.Info("Getting variable set...") + + existingEntity, err := h.variableSetManager.GetVariableSetByName(ctx, params.VariableSetName) + handler.HandleResult(w, r, ctx, err, existingEntity) + } +} + +// @Id listVariableSets +// @Summary List variable sets +// @Description List variable set information +// @Tags variable_set +// @Produce json +// @Param variableSetName query string false "Variable Set Name" +// @Param page query uint false "The current page to fetch. Default to 1" +// @Param pageSize query uint false "The size of the page. Default to 10" +// @Param sortBy query string false "Which field to sort the list by. Default to id" +// @Param ascending query bool false "Whether to sort the list in ascending order. Default to false" +// @Param fetchAll query bool false "Whether to list all the variable sets" +// @Success 200 {object} handler.Response{data=response.PaginatedVariableSetResponse} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variablesets [get] +func (h *Handler) ListVariableSets() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx := r.Context() + logger := logutil.GetLogger(ctx) + logger.Info("Listing variable sets...") + + query := r.URL.Query() + + // Get variable set filter. + filter, variableSetSortOptions, err := h.variableSetManager.BuildVariableSetFilterAndSortOptions(ctx, &query) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // List variable sets with pagination. + variableSetEntities, err := h.variableSetManager.ListVariableSets(ctx, filter, variableSetSortOptions) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + paginatedResponse := response.PaginatedVariableSetResponse{ + VariableSets: variableSetEntities.VariableSets, + Total: variableSetEntities.Total, + CurrentPage: filter.Pagination.Page, + PageSize: filter.Pagination.PageSize, + } + handler.HandleResult(w, r, ctx, err, paginatedResponse) + } +} + +// @Id listVariableSetsByLabels +// @Summary List variable sets by labels +// @Description List variable set information by label selectors +// @Tags variable_set +// @Produce json +// @Param selector query string true "Label selectors to match variable sets" +// @Success 200 {object} handler.Response{data=response.PaginatedVariableSetResponse} "Success" +// @Failure 400 {object} error "Bad Request" +// @Failure 401 {object} error "Unauthorized" +// @Failure 429 {object} error "Too Many Requests" +// @Failure 404 {object} error "Not Found" +// @Failure 500 {object} error "Internal Server Error" +// @Router /api/v1/variablesets/matched [get] +func (h *Handler) ListVariableSetsByLabels() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Getting stuff from context. + ctx := r.Context() + logger := logutil.GetLogger(ctx) + logger.Info("Listing variable sets by labels...") + + query := r.URL.Query() + + // List variable sets by label selectors. + selector := query.Get("selector") + if selector == "" { + render.Render(w, r, handler.FailureResponse(ctx, errors.New("empty label selectors"))) + return + } + + // Fetch all the variable sets to match the label selectors. + filter := &entity.VariableSetFilter{ + Pagination: &entity.Pagination{ + Page: constant.CommonPageDefault, + PageSize: constant.CommonPageSizeDefault, + }, + FetchAll: true, + } + sortOptions := &entity.SortOptions{ + Field: constant.SortByID, + Ascending: false, + } + + variableSetEntities, err := h.variableSetManager.ListVariableSets(ctx, filter, sortOptions) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + + // If the amount of variable sets exceeds the maximum result limit, + // then retrieve the complete result through a looped query. + for len(variableSetEntities.VariableSets) < variableSetEntities.Total { + filter = &entity.VariableSetFilter{ + Pagination: &entity.Pagination{ + Page: 2, + PageSize: len(variableSetEntities.VariableSets), + }, + FetchAll: true, + } + tmpVariableSetEntities, err := h.variableSetManager.ListVariableSets(ctx, filter, sortOptions) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + variableSetEntities.VariableSets = append(variableSetEntities.VariableSets, tmpVariableSetEntities.VariableSets...) + } + + var matchedVariableSets []*entity.VariableSet + + // Match the label selectors with Parser from `k8s.io/apimachinery/pkg/labels`. + labelSelector, err := labels.Parse(selector) + if err != nil { + render.Render(w, r, handler.FailureResponse(ctx, err)) + return + } + for _, vs := range variableSetEntities.VariableSets { + labelSet := labels.Set(vs.Labels) + if labelSelector.Matches(labelSet) { + matchedVariableSets = append(matchedVariableSets, vs) + } + } + + selectedResponse := response.SelectedVariableSetResponse{ + VariableSets: matchedVariableSets, + Total: len(matchedVariableSets), + } + handler.HandleResult(w, r, ctx, err, selectedResponse) + } +} + +func requestHelper(r *http.Request) (context.Context, *httplog.Logger, *VariableSetRequestParams, error) { + ctx := r.Context() + logger := logutil.GetLogger(ctx) + + // Get URL parameters. + variableSetName := chi.URLParam(r, "variableSetName") + if variableSetName == "" { + return nil, nil, nil, variableset.ErrEmptyVariableSetName + } + + params := VariableSetRequestParams{ + VariableSetName: variableSetName, + } + + return ctx, logger, ¶ms, nil +} diff --git a/pkg/server/handler/variableset/types.go b/pkg/server/handler/variableset/types.go new file mode 100644 index 000000000..f87152825 --- /dev/null +++ b/pkg/server/handler/variableset/types.go @@ -0,0 +1,19 @@ +package variableset + +import "kusionstack.io/kusion/pkg/server/manager/variableset" + +type Handler struct { + variableSetManager *variableset.VariableSetManager +} + +func NewHandler( + variableSetManager *variableset.VariableSetManager, +) (*Handler, error) { + return &Handler{ + variableSetManager: variableSetManager, + }, nil +} + +type VariableSetRequestParams struct { + VariableSetName string +} diff --git a/pkg/server/manager/variable/types.go b/pkg/server/manager/variable/types.go new file mode 100644 index 000000000..b2c8da1ac --- /dev/null +++ b/pkg/server/manager/variable/types.go @@ -0,0 +1,25 @@ +package variable + +import ( + "errors" + + "kusionstack.io/kusion/pkg/domain/repository" +) + +var ( + ErrGettingNonExistingVariable = errors.New("the variable does not exist") + ErrUpdatingNonExistingVariable = errors.New("the variable to update does not exist") + ErrEmptyVariableName = errors.New("the variable name should not be empty") +) + +type VariableManager struct { + variableRepo repository.VariableRepository +} + +func NewVariableManager( + variableRepo repository.VariableRepository, +) *VariableManager { + return &VariableManager{ + variableRepo: variableRepo, + } +} diff --git a/pkg/server/manager/variable/util.go b/pkg/server/manager/variable/util.go new file mode 100644 index 000000000..3e7583ec0 --- /dev/null +++ b/pkg/server/manager/variable/util.go @@ -0,0 +1,17 @@ +package variable + +import ( + "fmt" + + "kusionstack.io/kusion/pkg/domain/constant" +) + +func validateVariableSortOptions(sortBy string) (string, error) { + if sortBy == "" { + return constant.SortByID, nil + } + if sortBy != constant.SortByID && sortBy != constant.SortByName { + return "", fmt.Errorf("invalid sort option: %s. Can only sort by id, name", sortBy) + } + return sortBy, nil +} diff --git a/pkg/server/manager/variable/variable_manager.go b/pkg/server/manager/variable/variable_manager.go new file mode 100644 index 000000000..e832ac472 --- /dev/null +++ b/pkg/server/manager/variable/variable_manager.go @@ -0,0 +1,153 @@ +package variable + +import ( + "context" + "errors" + "net/url" + "strconv" + + "github.com/jinzhu/copier" + "gorm.io/gorm" + "kusionstack.io/kusion/pkg/domain/constant" + "kusionstack.io/kusion/pkg/domain/entity" + "kusionstack.io/kusion/pkg/domain/request" + logutil "kusionstack.io/kusion/pkg/server/util/logging" +) + +func (v *VariableManager) CreateVariable(ctx context.Context, + requestPayload request.CreateVariableRequest, +) (*entity.Variable, error) { + // Convert request payload to the domain model. + var createdEntity entity.Variable + if err := copier.Copy(&createdEntity, &requestPayload); err != nil { + return nil, err + } + + // Create variable with repository. + if err := v.variableRepo.Create(ctx, &createdEntity); err != nil { + return nil, err + } + + return &createdEntity, nil +} + +func (v *VariableManager) DeleteVariableByNameAndVariableSet(ctx context.Context, + name, variableSet string, +) error { + if err := v.variableRepo.Delete(ctx, name, variableSet); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrGettingNonExistingVariable + } + return err + } + + return nil +} + +func (v *VariableManager) UpdateVariableByNameAndVariableSet(ctx context.Context, + name, variableSet string, requestPayload request.UpdateVariableRequest, +) (*entity.Variable, error) { + // Convert request payload to domain model. + var requestEntity entity.Variable + if err := copier.Copy(&requestEntity, &requestPayload); err != nil { + return nil, err + } + + // Get the existing variable by name. + updatedEntity, err := v.variableRepo.Get(ctx, name, variableSet) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrUpdatingNonExistingVariable + } + + return nil, err + } + + // Overwrite non-zero values in request entity to existing entity. + copier.CopyWithOption(updatedEntity, requestEntity, copier.Option{IgnoreEmpty: true}) + + // Update variable with repository. + if err = v.variableRepo.Update(ctx, updatedEntity); err != nil { + return nil, err + } + + return updatedEntity, nil +} + +func (v *VariableManager) GetVariableByNameAndVariableSet(ctx context.Context, + name, variableSet string, +) (*entity.Variable, error) { + existingEntity, err := v.variableRepo.Get(ctx, name, variableSet) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrGettingNonExistingVariable + } + + return nil, err + } + + return existingEntity, nil +} + +func (v *VariableManager) ListVariables(ctx context.Context, + filter *entity.VariableFilter, sortOptions *entity.SortOptions, +) (*entity.VariableListResult, error) { + variableEntities, err := v.variableRepo.List(ctx, filter, sortOptions) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrGettingNonExistingVariable + } + + return nil, err + } + + return variableEntities, nil +} + +func (v *VariableManager) BuildVariableFilterAndSortOptions(ctx context.Context, + query *url.Values, +) (*entity.VariableFilter, *entity.SortOptions, error) { + logger := logutil.GetLogger(ctx) + logger.Info("Building variable filter and sort options...") + + variableNameParam := query.Get("variableName") + variableSetNameParam := query.Get("variableSetName") + fetchAllParam, _ := strconv.ParseBool(query.Get("fetchAll")) + + filter := entity.VariableFilter{} + if variableNameParam != "" { + filter.Name = variableNameParam + } + if variableSetNameParam != "" { + filter.VariableSet = variableSetNameParam + } + filter.FetchAll = fetchAllParam + + // Set pagination parameters. + page, _ := strconv.Atoi(query.Get("page")) + if page <= 0 { + page = constant.CommonPageDefault + } + pageSize, _ := strconv.Atoi(query.Get("pageSize")) + if pageSize <= 0 { + pageSize = constant.CommonPageSizeDefault + } + filter.Pagination = &entity.Pagination{ + Page: page, + PageSize: pageSize, + } + + // Build sort options + sortBy := query.Get("sortBy") + sortBy, err := validateVariableSortOptions(sortBy) + if err != nil { + return nil, nil, err + } + SortOrderAscending, _ := strconv.ParseBool(query.Get("ascending")) + variableSetSortOptions := &entity.SortOptions{ + Field: sortBy, + Ascending: SortOrderAscending, + } + + return &filter, variableSetSortOptions, nil +} diff --git a/pkg/server/manager/variableset/types.go b/pkg/server/manager/variableset/types.go new file mode 100644 index 000000000..dfa96ad4d --- /dev/null +++ b/pkg/server/manager/variableset/types.go @@ -0,0 +1,26 @@ +package variableset + +import ( + "errors" + + "kusionstack.io/kusion/pkg/domain/repository" +) + +var ( + ErrGettingNonExistingVariableSet = errors.New("the variable set does not exist") + ErrUpdatingNonExistingVariableSet = errors.New("the variable set to update does not exist") + ErrEmptyVariableSetName = errors.New("the variable set name should not be empty") + ErrEmptyVariableSetLabels = errors.New("the variable set labels should not be empty") +) + +type VariableSetManager struct { + variableSetRepo repository.VariableSetRepository +} + +func NewVariableSetManager( + variableSetRepo repository.VariableSetRepository, +) *VariableSetManager { + return &VariableSetManager{ + variableSetRepo: variableSetRepo, + } +} diff --git a/pkg/server/manager/variableset/util.go b/pkg/server/manager/variableset/util.go new file mode 100644 index 000000000..cba1e5443 --- /dev/null +++ b/pkg/server/manager/variableset/util.go @@ -0,0 +1,17 @@ +package variableset + +import ( + "fmt" + + "kusionstack.io/kusion/pkg/domain/constant" +) + +func validateVariableSetSortOptions(sortBy string) (string, error) { + if sortBy == "" { + return constant.SortByID, nil + } + if sortBy != constant.SortByID && sortBy != constant.SortByName { + return "", fmt.Errorf("invalid sort option: %s. Can only sort by id, name", sortBy) + } + return sortBy, nil +} diff --git a/pkg/server/manager/variableset/variable_set_manager.go b/pkg/server/manager/variableset/variable_set_manager.go new file mode 100644 index 000000000..6fabafc63 --- /dev/null +++ b/pkg/server/manager/variableset/variable_set_manager.go @@ -0,0 +1,145 @@ +package variableset + +import ( + "context" + "errors" + "net/url" + "strconv" + + "github.com/jinzhu/copier" + "gorm.io/gorm" + "kusionstack.io/kusion/pkg/domain/constant" + "kusionstack.io/kusion/pkg/domain/entity" + "kusionstack.io/kusion/pkg/domain/request" + logutil "kusionstack.io/kusion/pkg/server/util/logging" +) + +func (vs *VariableSetManager) CreateVariableSet(ctx context.Context, + requestPayload request.CreateVariableSetRequest, +) (*entity.VariableSet, error) { + // Convert request payload to the domain model. + var createdEntity entity.VariableSet + if err := copier.Copy(&createdEntity, &requestPayload); err != nil { + return nil, err + } + + // Create variable set with repository. + if err := vs.variableSetRepo.Create(ctx, &createdEntity); err != nil { + return nil, err + } + + return &createdEntity, nil +} + +func (vs *VariableSetManager) DeleteVariableSetByName(ctx context.Context, name string) error { + if err := vs.variableSetRepo.Delete(ctx, name); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrGettingNonExistingVariableSet + } + return err + } + + return nil +} + +func (vs *VariableSetManager) UpdateVariableSetByName(ctx context.Context, + name string, requestPayload request.UpdateVariableSetRequest, +) (*entity.VariableSet, error) { + // Convert request payload to domain model. + var requestEntity entity.VariableSet + if err := copier.Copy(&requestEntity, &requestPayload); err != nil { + return nil, err + } + + // Get the existing variable set by name. + updatedEntity, err := vs.variableSetRepo.Get(ctx, name) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrUpdatingNonExistingVariableSet + } + + return nil, err + } + + // Overwrite non-zero values in request entity to existing entity. + copier.CopyWithOption(updatedEntity, requestEntity, copier.Option{IgnoreEmpty: true}) + + // Update variable set with repository. + if err = vs.variableSetRepo.Update(ctx, updatedEntity); err != nil { + return nil, err + } + + return updatedEntity, nil +} + +func (vs *VariableSetManager) GetVariableSetByName(ctx context.Context, name string) (*entity.VariableSet, error) { + existingEntity, err := vs.variableSetRepo.Get(ctx, name) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrGettingNonExistingVariableSet + } + + return nil, err + } + + return existingEntity, nil +} + +func (vs *VariableSetManager) ListVariableSets(ctx context.Context, + filter *entity.VariableSetFilter, sortOptions *entity.SortOptions, +) (*entity.VariableSetListResult, error) { + variableSetEntities, err := vs.variableSetRepo.List(ctx, filter, sortOptions) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrGettingNonExistingVariableSet + } + + return nil, err + } + + return variableSetEntities, nil +} + +func (vs *VariableSetManager) BuildVariableSetFilterAndSortOptions(ctx context.Context, + query *url.Values, +) (*entity.VariableSetFilter, *entity.SortOptions, error) { + logger := logutil.GetLogger(ctx) + logger.Info("Building variable set filter and sort options...") + + variableSetNameParam := query.Get("variableSetName") + fetchAllParam, _ := strconv.ParseBool(query.Get("fetchAll")) + + filter := entity.VariableSetFilter{} + if variableSetNameParam != "" { + filter.Name = variableSetNameParam + } + filter.FetchAll = fetchAllParam + + // Set pagination parameters. + page, _ := strconv.Atoi(query.Get("page")) + if page <= 0 { + page = constant.CommonPageDefault + } + pageSize, _ := strconv.Atoi(query.Get("pageSize")) + if pageSize <= 0 { + pageSize = constant.CommonPageSizeDefault + } + filter.Pagination = &entity.Pagination{ + Page: page, + PageSize: pageSize, + } + + // Build sort options + sortBy := query.Get("sortBy") + sortBy, err := validateVariableSetSortOptions(sortBy) + if err != nil { + return nil, nil, err + } + SortOrderAscending, _ := strconv.ParseBool(query.Get("ascending")) + variableSetSortOptions := &entity.SortOptions{ + Field: sortBy, + Ascending: SortOrderAscending, + } + + return &filter, variableSetSortOptions, nil +} diff --git a/pkg/server/route/route.go b/pkg/server/route/route.go index dbb40675b..d872c409d 100644 --- a/pkg/server/route/route.go +++ b/pkg/server/route/route.go @@ -25,6 +25,8 @@ import ( "kusionstack.io/kusion/pkg/server/handler/resource" "kusionstack.io/kusion/pkg/server/handler/source" "kusionstack.io/kusion/pkg/server/handler/stack" + "kusionstack.io/kusion/pkg/server/handler/variable" + "kusionstack.io/kusion/pkg/server/handler/variableset" "kusionstack.io/kusion/pkg/server/handler/workspace" backendmanager "kusionstack.io/kusion/pkg/server/manager/backend" modulemanager "kusionstack.io/kusion/pkg/server/manager/module" @@ -33,6 +35,8 @@ import ( resourcemanager "kusionstack.io/kusion/pkg/server/manager/resource" sourcemanager "kusionstack.io/kusion/pkg/server/manager/source" stackmanager "kusionstack.io/kusion/pkg/server/manager/stack" + variablemanager "kusionstack.io/kusion/pkg/server/manager/variable" + variablesetmanager "kusionstack.io/kusion/pkg/server/manager/variableset" workspacemanager "kusionstack.io/kusion/pkg/server/manager/workspace" appmiddleware "kusionstack.io/kusion/pkg/server/middleware" authutil "kusionstack.io/kusion/pkg/server/util/auth" @@ -172,6 +176,8 @@ func setupRestAPIV1( resourceRepo := persistence.NewResourceRepository(config.DB) moduleRepo := persistence.NewModuleRepository(config.DB) runRepo := persistence.NewRunRepository(config.DB) + variablesetRepo := persistence.NewVariableSetRepository(config.DB) + variableRepo := persistence.NewVariableRepository(config.DB) stackManager := stackmanager.NewStackManager(stackRepo, projectRepo, workspaceRepo, resourceRepo, runRepo, config.DefaultBackend, config.MaxConcurrent) sourceManager := sourcemanager.NewSourceManager(sourceRepo) @@ -181,6 +187,8 @@ func setupRestAPIV1( projectManager := projectmanager.NewProjectManager(projectRepo, organizationRepo, sourceRepo, config.DefaultSource) resourceManager := resourcemanager.NewResourceManager(resourceRepo) moduleManager := modulemanager.NewModuleManager(moduleRepo, workspaceRepo, backendRepo) + variableSetManager := variablesetmanager.NewVariableSetManager(variablesetRepo) + variableManager := variablemanager.NewVariableManager(variableRepo) // Set up the handlers for the resources. sourceHandler, err := source.NewHandler(sourceManager) @@ -223,6 +231,16 @@ func setupRestAPIV1( logger.Error(err.Error(), "Error creating module handler", "error", err) return } + variableSetHandler, err := variableset.NewHandler(variableSetManager) + if err != nil { + logger.Error(err.Error(), "Error creating variable set handler", "error", err) + return + } + variableHandler, err := variable.NewHandler(variableManager) + if err != nil { + logger.Error(err.Error(), "Error creating variable handler", "error", err) + return + } // Set up the routes for the resources. r.Route("/sources", func(r chi.Router) { @@ -330,4 +348,25 @@ func setupRestAPIV1( r.Get("/", moduleHandler.GetModule()) }) }) + r.Route("/variablesets", func(r chi.Router) { + r.Post("/", variableSetHandler.CreateVariableSet()) + r.Get("/", variableSetHandler.ListVariableSets()) + r.Get("/matched", variableSetHandler.ListVariableSetsByLabels()) + r.Route("/{variableSetName}", func(r chi.Router) { + r.Delete("/", variableSetHandler.DeleteVariableSet()) + r.Put("/", variableSetHandler.UpdateVariableSet()) + r.Get("/", variableSetHandler.GetVariableSet()) + }) + }) + r.Route("/variables", func(r chi.Router) { + r.Post("/", variableHandler.CreateVariable()) + r.Get("/", variableHandler.ListVariables()) + r.Route("/{variableSetName}", func(r chi.Router) { + r.Route("/{variableName}", func(r chi.Router) { + r.Delete("/", variableHandler.DeleteVariable()) + r.Put("/", variableHandler.UpdateVariable()) + r.Get("/", variableHandler.GetVariable()) + }) + }) + }) }