96 lines
4.0 KiB
Go
96 lines
4.0 KiB
Go
package mysql
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||
)
|
||
|
||
// BootstrapInput 仅供持有数据库管理权限的初始化命令使用,不暴露为公共 HTTP。
|
||
type BootstrapInput struct {
|
||
Application string // Application 应用名称,每次初始化创建独立应用。
|
||
Namespace string // Namespace 命名空间名称。
|
||
Environment string // Environment 环境名称,必须显式指定。
|
||
Queue string // Queue 初始队列名称。
|
||
WorkerAccess bool // WorkerAccess 显式增加开发用 Worker 接入及消费权限。
|
||
}
|
||
|
||
// BootstrapResult 包含一次性交付的原始凭据,禁止记录到日志或提交到版本库。
|
||
type BootstrapResult struct {
|
||
ApplicationID core.ID `json:"application_id"` // ApplicationID 新建应用标识。
|
||
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 新建隔离范围。
|
||
QueueID core.ID `json:"queue_id"` // QueueID 初始队列标识。
|
||
CredentialID core.ID `json:"credential_id"` // CredentialID 凭据记录标识。
|
||
Token string `json:"token"` // Token 原始秘密,仅保存到受保护交付文件。
|
||
ExpiresAt time.Time `json:"expires_at"` // ExpiresAt 初始凭据到期时间,UTC。
|
||
}
|
||
|
||
func Bootstrap(ctx context.Context, db *sql.DB, in BootstrapInput, deliver func(BootstrapResult) error) error {
|
||
for _, v := range []string{in.Application, in.Namespace, in.Queue} {
|
||
if !service.ValidName(v, 128) {
|
||
return fmt.Errorf("invalid bootstrap resource name")
|
||
}
|
||
}
|
||
if !service.ValidName(in.Environment, 32) || deliver == nil {
|
||
return fmt.Errorf("environment and secure delivery are required")
|
||
}
|
||
if err := CheckSchema(ctx, db); err != nil {
|
||
return err
|
||
}
|
||
var out BootstrapResult
|
||
for _, dest := range []*core.ID{&out.ApplicationID, &out.NamespaceID, &out.QueueID, &out.CredentialID} {
|
||
v, err := core.NewID()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
*dest = v
|
||
}
|
||
secret, digest, err := auth.Issue(out.NamespaceID, out.CredentialID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
out.Token = secret
|
||
permissions := auth.Permissions{Actions: []string{"tasks:submit", "tasks:read", "tasks:cancel", "queues:read", "queues:manage"}, Queues: []string{"*"}}
|
||
if in.WorkerAccess {
|
||
permissions.Actions = append(permissions.Actions, "workers:connect", "workers:consume")
|
||
}
|
||
raw, err := json.Marshal(permissions)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return WithTx(ctx, db, func(tx *sql.Tx) error {
|
||
now, err := dbNow(ctx, tx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
out.ExpiresAt = now.Add(30 * 24 * time.Hour)
|
||
if _, err = tx.ExecContext(ctx, "INSERT INTO orbit_applications(id,name) VALUES (?,?)", out.ApplicationID, in.Application); err != nil {
|
||
return err
|
||
}
|
||
if _, err = tx.ExecContext(ctx, "INSERT INTO orbit_namespaces(id,application_id,name,environment) VALUES (?,?,?,?)", out.NamespaceID, out.ApplicationID, in.Namespace, in.Environment); err != nil {
|
||
return err
|
||
}
|
||
if _, err = tx.ExecContext(ctx, "INSERT INTO pulse_queues(namespace_id,id,name,max_concurrency,rate_per_second,policy) VALUES (?,?,?,4,10,'{}')", out.NamespaceID, out.QueueID, in.Queue); err != nil {
|
||
return err
|
||
}
|
||
if _, err = tx.ExecContext(ctx, "INSERT INTO orbit_credentials(namespace_id,id,name,secret_digest,permissions,expires_at) VALUES (?,?,'bootstrap',?,?,?)", out.NamespaceID, out.CredentialID, digest, string(raw), out.ExpiresAt); err != nil {
|
||
return err
|
||
}
|
||
op, err := core.NewID()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := appendAudit(ctx, tx, auth.Token{NamespaceID: out.NamespaceID, CredentialID: out.CredentialID}, op, out.CredentialID, "credential", "bootstrap.create", "database administrator bootstrap", nil, map[string]any{"permissions": permissions, "expires_at": out.ExpiresAt}); err != nil {
|
||
return err
|
||
}
|
||
// 交付文件先持久化再提交;提交失败时保留文件以便核对不确定结果。
|
||
return deliver(out)
|
||
})
|
||
}
|