Files
orbit/weave/definition/service.go

117 lines
6.0 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 definition
import (
"context"
"strings"
"time"
"unicode/utf8"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
"git.yuntuo.hk/k2safe/orbit/pulse/service"
)
const ReadAction = "workflows:definitions:read"
const PublishAction = "workflows:definitions:publish"
const MaxDefinitions = 100
const MaxVersions = 100
// CreateInput 创建命名空间内的稳定定义身份,不发布版本或启动实例。
type CreateInput struct {
Name string `json:"name"` // Name 命名空间内唯一ASCII名称,最长128字符。
Description string `json:"description"` // Description 用途说明,最多1024个Unicode字符。
}
// PublishInput 发布不可变版本;同版本内容冲突不能通过更换幂等键覆盖。
type PublishInput struct {
Version string `json:"version"` // Version 研发指定的ASCII版本名,最长64字符,不包含路径分隔符。
Specification Specification `json:"specification"` // Specification 完整且显式的冻结规则。
Reason string `json:"reason"` // Reason 非空发布原因,最多1024个字符,写入审计。
}
// Summary 仅含命名空间级元数据;读取完整版本还须授权所有引用队列。
type Summary struct {
ID core.ID `json:"id"` // ID 定义稳定标识。
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 定义隔离范围。
Name string `json:"name"` // Name 创建时冻结的唯一名称。
Description string `json:"description"` // Description 创建时冻结的用途说明。
Revision uint64 `json:"revision,string"` // Revision 从1开始,每个新发布版本增加1,不能替代业务版本名。
CreatedAt time.Time `json:"created_at"` // CreatedAt UTC数据库创建时间。
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最近新版本发布的UTC数据库时间。
}
// Analysis 是静态预算上界;不表示业务任务已执行或已分配配额。
type Analysis struct {
Steps uint32 `json:"steps"` // Steps 全定义静态步骤数。
MaxConcurrency uint32 `json:"max_concurrency"` // MaxConcurrency 并行业务任务上界。
MaxTaskAttempts uint32 `json:"max_task_attempts"` // MaxTaskAttempts 所有路径的保守尝试预算总和。
Queues []core.ID `json:"queues"` // Queues 排序去重队列,始终为数组。
}
// VersionView 返回原始发布内容和身份,不使用可变的latest别名。
type VersionView struct {
ID core.ID `json:"id"` // ID 不可变版本记录标识。
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 隔离范围。
DefinitionID core.ID `json:"definition_id"` // DefinitionID 父定义标识,由应用事务校验。
Version string `json:"version"` // Version 发布版本名。
Specification Specification `json:"specification"` // Specification 已冻结规则,数字精度不经float64转换。
ContentDigest string `json:"content_digest"` // ContentDigest 带域分隔的SHA-256,64位小写十六进制。
PublishedBy core.ID `json:"published_by"` // PublishedBy 原发布凭据标识,不包含凭据秘密。
PublishedAt time.Time `json:"published_at"` // PublishedAt 原发布时间,重复发布不更新。
Analysis Analysis `json:"analysis"` // Analysis 根据结构版本1规则计算的静态检查结果。
}
type CreateResult struct {
Definition Summary `json:"definition"` // Definition 创建事务的原始快照。
Deduplicated bool `json:"deduplicated"` // Deduplicated 是否返回原回执。
}
type PublishResult struct {
Version VersionView `json:"version"` // Version 原版本快照,不包含当前父定义状态。
Deduplicated bool `json:"deduplicated"` // Deduplicated 原键或同版本同内容重复请求。
}
type Store interface {
Create(context.Context, auth.Token, string, CreateInput) (CreateResult, error)
Get(context.Context, auth.Token, core.ID) (Summary, error)
Publish(context.Context, auth.Token, core.ID, string, PublishInput) (PublishResult, error)
Version(context.Context, auth.Token, core.ID, string) (VersionView, error)
}
type Service struct{ store Store }
func New(store Store) *Service { return &Service{store: store} }
func (s *Service) Create(ctx context.Context, t auth.Token, key string, in CreateInput) (CreateResult, error) {
if err := service.ValidateKey(key); err != nil {
return CreateResult{}, err
}
if !name(in.Name, 128) || !textValid(in.Description, false) {
return CreateResult{}, fault.Invalid("invalid workflow definition name or description")
}
return s.store.Create(ctx, t, key, in)
}
func textValid(s string, required bool) bool {
return utf8.ValidString(s) && utf8.RuneCountInString(s) <= 1024 && !strings.ContainsRune(s, 0) && (!required || strings.TrimSpace(s) != "")
}
func (s *Service) Get(ctx context.Context, t auth.Token, id core.ID) (Summary, error) {
if id.Validate() != nil {
return Summary{}, fault.Invalid("invalid definition id")
}
return s.store.Get(ctx, t, id)
}
func (s *Service) Publish(ctx context.Context, t auth.Token, id core.ID, key string, in PublishInput) (PublishResult, error) {
if err := service.ValidateKey(key); err != nil {
return PublishResult{}, err
}
if id.Validate() != nil || !name(in.Version, 64) || !textValid(in.Reason, true) {
return PublishResult{}, fault.Invalid("valid definition id, version and reason required")
}
if _, err := Check(in.Specification); err != nil {
return PublishResult{}, fault.Invalid(err.Error())
}
return s.store.Publish(ctx, t, id, key, in)
}
func (s *Service) Version(ctx context.Context, t auth.Token, id core.ID, version string) (VersionView, error) {
if id.Validate() != nil || !name(version, 64) {
return VersionView{}, fault.Invalid("invalid definition id or version")
}
return s.store.Version(ctx, t, id, version)
}