Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: server supports getting workspace modules #1313

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/openapispec/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ const docTemplate = `{
],
"summary": "List module",
"operationId": "listModule",
"parameters": [
{
"type": "integer",
"description": "Workspace ID to filter module list by. Default to all workspaces.",
"name": "workspaceID",
"in": "query"
}
],
"responses": {
"200": {
"description": "Success",
Expand Down
8 changes: 8 additions & 0 deletions api/openapispec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,14 @@
],
"summary": "List module",
"operationId": "listModule",
"parameters": [
{
"type": "integer",
"description": "Workspace ID to filter module list by. Default to all workspaces.",
"name": "workspaceID",
"in": "query"
}
],
"responses": {
"200": {
"description": "Success",
Expand Down
5 changes: 5 additions & 0 deletions api/openapispec/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,11 @@ paths:
get:
description: List module information
operationId: listModule
parameters:
- description: Workspace ID to filter module list by. Default to all workspaces.
in: query
name: workspaceID
type: integer
produces:
- application/json
responses:
Expand Down
17 changes: 17 additions & 0 deletions pkg/domain/entity/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ type Module struct {
Doc *url.URL `yaml:"doc,omitempty" json:"doc,omitempty"`
}

// ModuleWithVersion represents the specific configuration code module with version,
// which should be a specific instance of the Kusion Module provider.
type ModuleWithVersion struct {
// Name is the module name.
Name string `yaml:"name" json:"name"`
// URL is the module oci artifact registry URL.
URL *url.URL `yaml:"url" json:"url"`
// Version is the module oci artifact version.
Version string `yaml:"version" json:"version"`
// Description is a human-readable description of the module.
Description string `yaml:"description,omitempty" json:"description,omitempty"`
// Owners is a list of owners for the module.
Owners []string `yaml:"owners,omitempty" json:"owners,omitempty"`
// Doc is the documentation URL of the module.
Doc *url.URL `yaml:"doc,omitempty" json:"doc,omitempty"`
}

// Validate checks if the module is valid.
// It returns an error if the module is not valid.
func (m *Module) Validate() error {
Expand Down
32 changes: 24 additions & 8 deletions pkg/server/handler/module/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package module
import (
"context"
"net/http"
"strconv"

"github.com/go-chi/chi/v5"
"github.com/go-chi/httplog/v2"
Expand Down Expand Up @@ -89,7 +90,7 @@ func (h *Handler) DeleteModule() http.HandlerFunc {
// @Failure 429 {object} error "Too Many Requests"
// @Failure 404 {object} error "Not Found"
// @Failure 500 {object} error "Internal Server Error"
// @Router /api/v1/modules/{name} [put]
// @Router /api/v1/modules/{name} [put]
func (h *Handler) UpdateModule() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Getting stuff from context.
Expand Down Expand Up @@ -125,7 +126,7 @@ func (h *Handler) UpdateModule() http.HandlerFunc {
// @Failure 429 {object} error "Too Many Requests"
// @Failure 404 {object} error "Not Found"
// @Failure 500 {object} error "Internal Server Error"
// @Router /api/v1/modules/{name} [get]
// @Router /api/v1/modules/{name} [get]
func (h *Handler) GetModule() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Getting stuff from context.
Expand All @@ -146,12 +147,13 @@ func (h *Handler) GetModule() http.HandlerFunc {
// @Description List module information
// @Tags module
// @Produce json
// @Success 200 {object} []entity.Module "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"
// @Param workspaceID query uint false "Workspace ID to filter module list by. Default to all workspaces."
// @Success 200 {object} []entity.Module "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/modules [get]
func (h *Handler) ListModules() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -160,6 +162,20 @@ func (h *Handler) ListModules() http.HandlerFunc {
logger := logutil.GetLogger(ctx)
logger.Info("Listing module...")

wsIDParam := r.URL.Query().Get("workspaceID")
if wsIDParam != "" {
wsID, err := strconv.Atoi(wsIDParam)
if err != nil {
render.Render(w, r, handler.FailureResponse(ctx, modulemanager.ErrInvalidWorkspaceID))
return
}

// List modules in the specified workspace.
moduleEntities, err := h.moduleManager.ListModulesByWorkspaceID(ctx, uint(wsID))
handler.HandleResult(w, r, ctx, err, moduleEntities)
return
}

// List modules.
moduleEntities, err := h.moduleManager.ListModules(ctx)
handler.HandleResult(w, r, ctx, err, moduleEntities)
Expand Down
3 changes: 2 additions & 1 deletion pkg/server/handler/module/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ func NewHandler(
}

type ModuleRequestParams struct {
ModuleName string
ModuleName string
WorkspaceID uint
}
58 changes: 58 additions & 0 deletions pkg/server/manager/module/module_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"gorm.io/gorm"
"kusionstack.io/kusion/pkg/domain/entity"
"kusionstack.io/kusion/pkg/domain/request"
"kusionstack.io/kusion/pkg/server/manager/workspace"
)

func (m *ModuleManager) CreateModule(ctx context.Context, requestPayload request.CreateModuleRequest) (*entity.Module, error) {
Expand Down Expand Up @@ -116,3 +117,60 @@ func (m *ModuleManager) ListModules(ctx context.Context) ([]*entity.Module, erro

return moduleEntities, nil
}

func (m *ModuleManager) ListModulesByWorkspaceID(ctx context.Context, workspaceID uint) ([]*entity.ModuleWithVersion, error) {
// Get workspace entity by ID.
existingEntity, err := m.workspaceRepo.Get(ctx, workspaceID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, workspace.ErrGettingNonExistingWorkspace
}
return nil, err
}

// Get backend by backend ID.
backendEntity, err := m.backendRepo.Get(ctx, existingEntity.Backend.ID)
if err != nil && err == gorm.ErrRecordNotFound {
return nil, workspace.ErrBackendNotFound
} else if err != nil {
return nil, err
}

// Generate backend from the backend entity.
remoteBackend, err := workspace.NewBackendFromEntity(*backendEntity)
if err != nil {
return nil, err
}

// Get workspace storage from backend.
wsStorage, err := remoteBackend.WorkspaceStorage()
if err != nil {
return nil, err
}

// Get workspace config from storage.
ws, err := wsStorage.Get(existingEntity.Name)
if err != nil {
return nil, err
}

// Get the modules in the workspace.
moduleEntities := make([]*entity.ModuleWithVersion, 0, len(ws.Modules))
for moduleName, moduleConfigs := range ws.Modules {
moduleEntity, err := m.moduleRepo.Get(ctx, moduleName)
if err != nil {
return nil, err
}

moduleEntities = append(moduleEntities, &entity.ModuleWithVersion{
Name: moduleEntity.Name,
URL: moduleEntity.URL,
Version: moduleConfigs.Version,
Description: moduleEntity.Description,
Owners: moduleEntity.Owners,
Doc: moduleEntity.Doc,
})
}

return moduleEntities, nil
}
14 changes: 11 additions & 3 deletions pkg/server/manager/module/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ var (
ErrGettingNonExistingModule = errors.New("the module does not exist")
ErrUpdatingNonExistingModule = errors.New("the module to update does not exist")
ErrEmptyModuleName = errors.New("the module name should not be empty")
ErrInvalidWorkspaceID = errors.New("the workspace id is invalid")
)

type ModuleManager struct {
moduleRepo repository.ModuleRepository
moduleRepo repository.ModuleRepository
workspaceRepo repository.WorkspaceRepository
backendRepo repository.BackendRepository
}

func NewModuleManager(moduleRepo repository.ModuleRepository) *ModuleManager {
func NewModuleManager(moduleRepo repository.ModuleRepository,
workspaceRepo repository.WorkspaceRepository,
backendRepo repository.BackendRepository,
) *ModuleManager {
return &ModuleManager{
moduleRepo: moduleRepo,
moduleRepo: moduleRepo,
workspaceRepo: workspaceRepo,
backendRepo: backendRepo,
}
}
2 changes: 1 addition & 1 deletion pkg/server/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func setupRestAPIV1(
workspaceManager := workspacemanager.NewWorkspaceManager(workspaceRepo, backendRepo, config.DefaultBackend)
projectManager := projectmanager.NewProjectManager(projectRepo, organizationRepo, sourceRepo, config.DefaultSource)
resourceManager := resourcemanager.NewResourceManager(resourceRepo)
moduleManager := modulemanager.NewModuleManager(moduleRepo)
moduleManager := modulemanager.NewModuleManager(moduleRepo, workspaceRepo, backendRepo)

// Set up the handlers for the resources.
sourceHandler, err := source.NewHandler(sourceManager)
Expand Down
Loading