feat: publish immutable workflow definitions with typed SDK
This commit is contained in:
116
weave/definition/service.go
Normal file
116
weave/definition/service.go
Normal file
@@ -0,0 +1,116 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user