mirror of
https://github.com/vxcontrol/cloud.git
synced 2026-08-27 01:41:15 -04:00
242b79e97a
- Split reported components into images vs. files, add update strategies and per-stack resolution, and a shared action/reason vocabulary for update answers - Add models.ParseEnvelope[T] and MsgLogTypeWait to match the server's response contract; fix SDK retries silently resending an exhausted request body - Update examples/report-errors to continue issues via -issue-id and render streamed answers live; refresh README/API.md/doc.go for the license key flow
185 lines
6.3 KiB
Go
185 lines
6.3 KiB
Go
package models
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"reflect"
|
|
"regexp"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
const (
|
|
// semverRegexString accepts a version core with an optional revision after a
|
|
// dash, and optional build metadata after a plus. The revision is present on
|
|
// builds cut from a branch, where the commit is not attached to a tag
|
|
// ("2.0.0-87ac00f"), and absent on release builds ("2.0.0") — both are valid
|
|
// and both must round-trip unchanged.
|
|
//
|
|
// A value that fits neither form (a branch name such as "develop", an empty
|
|
// string) must be normalised by the client before it is sent — "0.0.0" is
|
|
// the conventional stand-in for "some unknown build". The literal "latest"
|
|
// is accepted here because package lookups use it to mean "the newest
|
|
// published version"; it is meaningless as an installer version.
|
|
semverRegexString = `^(([0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?)|latest)$`
|
|
semverexRegexString = `^(((v)?[0-9]+\.[0-9]+(\.[0-9]+)?(\.[0-9]+)?(-[a-zA-Z0-9-]+)?)|latest)$`
|
|
)
|
|
|
|
var (
|
|
validate *validator.Validate
|
|
)
|
|
|
|
func GetValidator() *validator.Validate {
|
|
return validate
|
|
}
|
|
|
|
// IValid is interface to control all models from user code
|
|
type IValid interface {
|
|
Valid() error
|
|
}
|
|
|
|
// IQuery is interface to control all models from user code
|
|
type IQuery interface {
|
|
Query() map[string]string
|
|
}
|
|
|
|
func templateValidatorString(regexpString string) validator.Func {
|
|
regexpValue := regexp.MustCompile(regexpString)
|
|
return func(fl validator.FieldLevel) bool {
|
|
field := fl.Field()
|
|
matchString := func(str string) bool {
|
|
if str == "" && fl.Param() == "omitempty" {
|
|
return true
|
|
}
|
|
return regexpValue.MatchString(str)
|
|
}
|
|
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
return matchString(fl.Field().String())
|
|
case reflect.Slice, reflect.Array:
|
|
for i := 0; i < field.Len(); i++ {
|
|
if !matchString(field.Index(i).String()) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Map:
|
|
for _, k := range field.MapKeys() {
|
|
if !matchString(field.MapIndex(k).String()) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// fileComponentValidator backs the `filecomp` tag: the field names a component
|
|
// that is delivered as a FILE.
|
|
//
|
|
// It reads the static table in artefacts.go instead of an `oneof=` list. The
|
|
// hand-written copies of that list — one here, three on the service side — had
|
|
// already drifted into three different answers, and this one was the narrowest:
|
|
// it refused downloads the service would have served.
|
|
//
|
|
// A nil pointer passes: presence is `required`'s business, not this tag's.
|
|
func fileComponentValidator() validator.Func {
|
|
var cb func(reflect.Value) bool
|
|
cb = func(field reflect.Value) bool {
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
return ComponentType(field.String()).IsFileComponent()
|
|
case reflect.Pointer:
|
|
if field.IsNil() {
|
|
return true
|
|
}
|
|
return cb(field.Elem())
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return func(fl validator.FieldLevel) bool {
|
|
return cb(fl.Field())
|
|
}
|
|
}
|
|
|
|
func deepValidator() validator.Func {
|
|
return func(fl validator.FieldLevel) bool {
|
|
if iv, ok := fl.Field().Interface().(IValid); ok {
|
|
if err := iv.Valid(); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
func getSignaturePublicKey() ed25519.PublicKey {
|
|
rawPublic := [40]byte{
|
|
0x37, 0xEC, 0xF3, 0xAB, 0xDF, 0x9C, 0xCB, 0x9E,
|
|
0xE0, 0x39, 0xA0, 0x99, 0xAB, 0x51, 0x3F, 0x16,
|
|
0x40, 0x8A, 0x8E, 0x18, 0x90, 0x77, 0xD0, 0x05,
|
|
0xED, 0x4C, 0x37, 0x5D, 0x28, 0x01, 0xA8, 0x7E,
|
|
0x58, 0x65, 0x84, 0xA8, 0x64, 0x40, 0xD0, 0x60,
|
|
}
|
|
|
|
result := [40]byte{rawPublic[0]>>4 | rawPublic[0]<<4}
|
|
for i := 1; i < 40; i++ {
|
|
result[i] = result[i-1] ^ rawPublic[i-1] ^ rawPublic[i]
|
|
}
|
|
|
|
return ed25519.PublicKey(result[result[37]:result[39]])
|
|
}
|
|
|
|
func init() {
|
|
validate = validator.New()
|
|
_ = validate.RegisterValidation("semver", templateValidatorString(semverRegexString))
|
|
_ = validate.RegisterValidation("semverex", templateValidatorString(semverexRegexString))
|
|
_ = validate.RegisterValidation("filecomp", fileComponentValidator())
|
|
_ = validate.RegisterValidation("valid", deepValidator())
|
|
|
|
_, _ = reflect.ValueOf(ComponentType("")).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(ComponentStatus("")).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(ProductStack("")).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(OSType("")).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(ArchType("")).Interface().(IValid)
|
|
|
|
// signature service models validation
|
|
_, _ = reflect.ValueOf(SignatureValue("")).Interface().(IValid)
|
|
|
|
// update service models validation
|
|
_, _ = reflect.ValueOf(UpdateStrategy("")).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(CheckUpdatesRequest{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(ImageComponentInfo{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(FileComponentInfo{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(CheckUpdatesResponse{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(UpdateInfo{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(ImageUpdate{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(FileUpdate{}).Interface().(IValid)
|
|
|
|
// package service models validation
|
|
_, _ = reflect.ValueOf(PackageInfoRequest{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(PackageInfoRequest{}).Interface().(IQuery)
|
|
_, _ = reflect.ValueOf(PackageInfoResponse{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(DownloadPackageRequest{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(DownloadPackageRequest{}).Interface().(IQuery)
|
|
|
|
// support service models validation
|
|
_, _ = reflect.ValueOf(SupportErrorRequest{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportErrorResponse{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportIssueRequest{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportIssueResponse{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportLogs{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportInvestigationRequest{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportInvestigationResponse{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(MsgLogType("")).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportMsgLog{}).Interface().(IValid)
|
|
_, _ = reflect.ValueOf(SupportIssueMessagesResponse{}).Interface().(IValid)
|
|
|
|
// product.go — what a running product tells the service about itself
|
|
_, _ = reflect.ValueOf(ProductUpdateRequest{}).Interface().(IValid)
|
|
}
|