Files
orbit/internal/storage/mysql/bootstrap.go

92 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 初始队列名称。
}
// 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{"*"}}
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)
})
}