mirror of
https://github.com/onepanelio/onepanel.git
synced 2025-10-05 21:56:50 +08:00
401 lines
11 KiB
Go
401 lines
11 KiB
Go
package v1
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
sq "github.com/Masterminds/squirrel"
|
|
"github.com/asaskevich/govalidator"
|
|
"github.com/lib/pq"
|
|
"github.com/onepanelio/core/pkg/util"
|
|
"github.com/onepanelio/core/pkg/util/pagination"
|
|
"github.com/onepanelio/core/pkg/util/ptr"
|
|
"google.golang.org/grpc/codes"
|
|
"time"
|
|
)
|
|
|
|
func (c *Client) workspacesSelectBuilder(namespace string) sq.SelectBuilder {
|
|
sb := sb.Select(getWorkspaceColumns("w", "")...).
|
|
Columns(getWorkspaceStatusColumns("w", "status")...).
|
|
Columns(getWorkspaceTemplateColumns("wt", "workspace_template")...).
|
|
Columns(getWorkflowTemplateVersionColumns("wftv", "workflow_template_version")...).
|
|
Columns("wtv.version \"workspace_template.version\"").
|
|
From("workspaces w").
|
|
Join("workspace_templates wt ON wt.id = w.workspace_template_id").
|
|
Join("workspace_template_versions wtv ON wtv.workspace_template_id = wt.id AND wtv.version = w.workspace_template_version").
|
|
Join("workflow_template_versions wftv ON wftv.workflow_template_id = wt.workflow_template_id AND wftv.version = w.workspace_template_version").
|
|
Where(sq.Eq{
|
|
"w.namespace": namespace,
|
|
})
|
|
|
|
return sb
|
|
}
|
|
|
|
func getWorkspaceParameterValue(parameters []Parameter, name string) *string {
|
|
for _, p := range parameters {
|
|
if p.Name == name {
|
|
return p.Value
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func mergeWorkspaceParameters(existingParameters, newParameters []Parameter) (parameters []Parameter) {
|
|
parameterMap := make(map[string]*string, 0)
|
|
for _, p := range newParameters {
|
|
parameterMap[p.Name] = p.Value
|
|
parameters = append(parameters, Parameter{
|
|
Name: p.Name,
|
|
Value: p.Value,
|
|
})
|
|
}
|
|
|
|
for _, p := range existingParameters {
|
|
_, ok := parameterMap[p.Name]
|
|
if !ok {
|
|
parameters = append(parameters, Parameter{
|
|
Name: p.Name,
|
|
Value: p.Value,
|
|
})
|
|
}
|
|
}
|
|
|
|
return parameters
|
|
}
|
|
|
|
// Injects parameters into the workspace.Parameters.
|
|
// If the parameter already exists, it's value is updated.
|
|
// The parameters injected are:
|
|
// sys-name
|
|
// sys-workspace-action
|
|
// sys-resource-action
|
|
// sys-host
|
|
func injectWorkspaceSystemParameters(namespace string, workspace *Workspace, workspaceAction, resourceAction string, config map[string]string) (err error) {
|
|
host := fmt.Sprintf("%v--%v.%v", workspace.Name, namespace, config["ONEPANEL_DOMAIN"])
|
|
if _, err = workspace.GenerateUID(); err != nil {
|
|
return
|
|
}
|
|
|
|
systemParameters := []Parameter{
|
|
{
|
|
Name: "sys-workspace-action",
|
|
Value: ptr.String(workspaceAction),
|
|
},
|
|
{
|
|
Name: "sys-resource-action",
|
|
Value: ptr.String(resourceAction),
|
|
},
|
|
{
|
|
Name: "sys-host",
|
|
Value: ptr.String(host),
|
|
},
|
|
}
|
|
workspace.Parameters = mergeWorkspaceParameters(workspace.Parameters, systemParameters)
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) createWorkspace(namespace string, parameters []byte, workspace *Workspace) (*Workspace, error) {
|
|
_, err := c.CreateWorkflowExecution(namespace, &WorkflowExecution{
|
|
Parameters: workspace.Parameters,
|
|
WorkflowTemplate: workspace.WorkspaceTemplate.WorkflowTemplate,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = sb.Insert("workspaces").
|
|
SetMap(sq.Eq{
|
|
"uid": workspace.UID,
|
|
"name": workspace.Name,
|
|
"namespace": namespace,
|
|
"parameters": parameters,
|
|
"phase": WorkspaceLaunching,
|
|
"started_at": time.Now().UTC(),
|
|
"workspace_template_id": workspace.WorkspaceTemplate.ID,
|
|
"workspace_template_version": workspace.WorkspaceTemplate.Version,
|
|
"url": workspace.URL,
|
|
}).
|
|
Suffix("RETURNING id, created_at").
|
|
RunWith(c.DB).
|
|
QueryRow().
|
|
Scan(&workspace.ID, &workspace.CreatedAt)
|
|
if err != nil {
|
|
return nil, util.NewUserErrorWrap(err, "Workspace")
|
|
}
|
|
|
|
return workspace, nil
|
|
}
|
|
|
|
// CreateWorkspace creates a workspace by triggering the corresponding workflow
|
|
func (c *Client) CreateWorkspace(namespace string, workspace *Workspace) (*Workspace, error) {
|
|
config, err := c.GetSystemConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parameters, err := json.Marshal(workspace.Parameters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = injectWorkspaceSystemParameters(namespace, workspace, "create", "apply", config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
workspace.Parameters = append(workspace.Parameters, Parameter{
|
|
Name: "sys-uid",
|
|
Value: ptr.String(workspace.UID),
|
|
})
|
|
|
|
sysHost := getWorkspaceParameterValue(workspace.Parameters, "sys-host")
|
|
if sysHost == nil {
|
|
return nil, fmt.Errorf("sys-host parameter not found")
|
|
}
|
|
workspace.URL = *sysHost
|
|
|
|
existingWorkspace, err := c.GetWorkspace(namespace, workspace.UID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existingWorkspace != nil {
|
|
return nil, util.NewUserError(codes.AlreadyExists, "Workspace already exists.")
|
|
}
|
|
|
|
// Validate workspace fields
|
|
valid, err := govalidator.ValidateStruct(workspace)
|
|
if err != nil || !valid {
|
|
return nil, util.NewUserError(codes.InvalidArgument, err.Error())
|
|
}
|
|
|
|
workspaceTemplate, err := c.GetWorkspaceTemplate(namespace,
|
|
workspace.WorkspaceTemplate.UID, workspace.WorkspaceTemplate.Version)
|
|
if err != nil {
|
|
return nil, util.NewUserError(codes.NotFound, "Workspace template not found.")
|
|
}
|
|
workspace.WorkspaceTemplate = workspaceTemplate
|
|
|
|
workspace, err = c.createWorkspace(namespace, parameters, workspace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := c.InsertLabels(TypeWorkspace, workspace.ID, workspace.Labels); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return workspace, nil
|
|
}
|
|
|
|
func (c *Client) GetWorkspace(namespace, uid string) (workspace *Workspace, err error) {
|
|
query, args, err := c.workspacesSelectBuilder(namespace).
|
|
Where(sq.And{
|
|
sq.Eq{"w.uid": uid},
|
|
sq.NotEq{"w.phase": WorkspaceTerminated},
|
|
}).ToSql()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
workspace = &Workspace{}
|
|
if err = c.DB.Get(workspace, query, args...); err == sql.ErrNoRows {
|
|
err = nil
|
|
workspace = nil
|
|
|
|
return
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = json.Unmarshal(workspace.ParametersBytes, &workspace.Parameters); err != nil {
|
|
return
|
|
}
|
|
|
|
labelsMap, err := c.GetDbLabelsMapped(TypeWorkspace, workspace.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
workspace.Labels = labelsMap[workspace.ID]
|
|
|
|
return
|
|
}
|
|
|
|
// UpdateWorkspaceStatus updates workspace status and times based on phase
|
|
func (c *Client) UpdateWorkspaceStatus(namespace, uid string, status *WorkspaceStatus) (err error) {
|
|
fieldMap := sq.Eq{
|
|
"phase": status.Phase,
|
|
"modified_at": time.Now().UTC(),
|
|
}
|
|
switch status.Phase {
|
|
case WorkspaceLaunching:
|
|
fieldMap["paused_at"] = pq.NullTime{}
|
|
fieldMap["started_at"] = time.Now().UTC()
|
|
break
|
|
case WorkspacePausing:
|
|
fieldMap["started_at"] = pq.NullTime{}
|
|
fieldMap["paused_at"] = time.Now().UTC()
|
|
break
|
|
case WorkspaceUpdating:
|
|
fieldMap["paused_at"] = pq.NullTime{}
|
|
fieldMap["updated_at"] = time.Now().UTC()
|
|
break
|
|
case WorkspaceTerminating:
|
|
fieldMap["started_at"] = pq.NullTime{}
|
|
fieldMap["paused_at"] = pq.NullTime{}
|
|
fieldMap["terminated_at"] = time.Now().UTC()
|
|
break
|
|
}
|
|
_, err = sb.Update("workspaces").
|
|
SetMap(fieldMap).
|
|
Where(sq.And{
|
|
sq.Eq{
|
|
"namespace": namespace,
|
|
"uid": uid,
|
|
}, sq.NotEq{
|
|
"phase": WorkspaceTerminated,
|
|
},
|
|
}).
|
|
RunWith(c.DB).Exec()
|
|
if err != nil {
|
|
return util.NewUserError(codes.NotFound, "Workspace not found.")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) ListWorkspaces(namespace string, paginator *pagination.PaginationRequest) (workspaces []*Workspace, err error) {
|
|
sb := sb.Select(getWorkspaceColumns("w", "")...).
|
|
Columns(getWorkspaceStatusColumns("w", "status")...).
|
|
Columns(getWorkspaceTemplateColumns("wt", "workspace_template")...).
|
|
From("workspaces w").
|
|
Join("workspace_templates wt ON wt.id = w.workspace_template_id").
|
|
OrderBy("w.created_at DESC").
|
|
Where(sq.And{
|
|
sq.Eq{
|
|
"w.namespace": namespace,
|
|
},
|
|
sq.NotEq{
|
|
"phase": WorkspaceTerminated,
|
|
},
|
|
})
|
|
sb = *paginator.ApplyToSelect(&sb)
|
|
|
|
query, args, err := sb.ToSql()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := c.DB.Select(&workspaces, query, args...); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) CountWorkspaces(namespace string) (count int, err error) {
|
|
err = sb.Select("COUNT( DISTINCT( w.id ))").
|
|
From("workspaces w").
|
|
Join("workspace_templates wt ON w.workspace_template_id = wt.id").
|
|
Where(sq.And{
|
|
sq.Eq{
|
|
"w.namespace": namespace,
|
|
},
|
|
sq.NotEq{
|
|
"phase": WorkspaceTerminated,
|
|
},
|
|
}).
|
|
RunWith(c.DB.DB).
|
|
QueryRow().
|
|
Scan(&count)
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) updateWorkspace(namespace, uid, workspaceAction, resourceAction string, status *WorkspaceStatus, parameters ...Parameter) (err error) {
|
|
workspace, err := c.GetWorkspace(namespace, uid)
|
|
if err != nil {
|
|
return util.NewUserError(codes.NotFound, "Workspace not found.")
|
|
}
|
|
|
|
config, err := c.GetSystemConfig()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
workspace.Parameters = mergeWorkspaceParameters(workspace.Parameters, parameters)
|
|
parametersJSON, err := json.Marshal(workspace.Parameters)
|
|
if err != nil {
|
|
return
|
|
}
|
|
workspace.Parameters = append(workspace.Parameters, Parameter{
|
|
Name: "sys-uid",
|
|
Value: ptr.String(uid),
|
|
})
|
|
err = injectWorkspaceSystemParameters(namespace, workspace, workspaceAction, resourceAction, config)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
workspaceTemplate, err := c.GetWorkspaceTemplate(namespace,
|
|
workspace.WorkspaceTemplate.UID, workspace.WorkspaceTemplate.Version)
|
|
if err != nil {
|
|
return util.NewUserError(codes.NotFound, "Workspace template not found.")
|
|
}
|
|
workspace.WorkspaceTemplate = workspaceTemplate
|
|
|
|
_, err = c.CreateWorkflowExecution(namespace, &WorkflowExecution{
|
|
Parameters: workspace.Parameters,
|
|
WorkflowTemplate: workspace.WorkspaceTemplate.WorkflowTemplate,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if err = c.UpdateWorkspaceStatus(namespace, uid, status); err != nil {
|
|
return
|
|
}
|
|
|
|
// Update parameters if they are passed
|
|
if len(parameters) == 0 {
|
|
return
|
|
}
|
|
|
|
_, err = sb.Update("workspaces").
|
|
SetMap(sq.Eq{
|
|
"parameters": parametersJSON,
|
|
}).
|
|
Where(sq.And{
|
|
sq.Eq{
|
|
"namespace": namespace,
|
|
"uid": uid,
|
|
}, sq.NotEq{
|
|
"phase": WorkspaceTerminated,
|
|
},
|
|
}).
|
|
RunWith(c.DB).Exec()
|
|
if err != nil {
|
|
return util.NewUserError(codes.NotFound, "Workspace not found.")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) UpdateWorkspace(namespace, uid string, parameters []Parameter) (err error) {
|
|
return c.updateWorkspace(namespace, uid, "update", "apply", &WorkspaceStatus{Phase: WorkspaceUpdating}, parameters...)
|
|
}
|
|
|
|
func (c *Client) PauseWorkspace(namespace, uid string) (err error) {
|
|
return c.updateWorkspace(namespace, uid, "pause", "delete", &WorkspaceStatus{Phase: WorkspacePausing})
|
|
}
|
|
|
|
func (c *Client) ResumeWorkspace(namespace, uid string) (err error) {
|
|
return c.updateWorkspace(namespace, uid, "create", "apply", &WorkspaceStatus{Phase: WorkspaceLaunching})
|
|
}
|
|
|
|
func (c *Client) DeleteWorkspace(namespace, uid string) (err error) {
|
|
return c.updateWorkspace(namespace, uid, "delete", "delete", &WorkspaceStatus{Phase: WorkspaceTerminating})
|
|
}
|