|
|
|
|
package update
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"io"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"tera-clone/services/tera-api/internal/storage/capacity"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
ApplyCapabilityValidationOnly = "validation-only"
|
|
|
|
|
ApplyCapabilityRecoveryV1 = "recovery-v1"
|
|
|
|
|
RecoveryCapabilityOfflineV1 = "offline-resumable-v1"
|
|
|
|
|
|
|
|
|
|
StateIdle = "idle"
|
|
|
|
|
StateReceiving = "receiving"
|
|
|
|
|
StateStaged = "staged"
|
|
|
|
|
StateValidating = "validating"
|
|
|
|
|
StateValidated = "validated"
|
|
|
|
|
StateApplyPending = "apply_pending"
|
|
|
|
|
StateRebootPending = "reboot_pending"
|
|
|
|
|
StateApplying = "applying"
|
|
|
|
|
StateVerifying = "verifying"
|
|
|
|
|
StateSucceeded = "succeeded"
|
|
|
|
|
StateFailed = "failed"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Payload struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
Class string `json:"class"`
|
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
|
SHA256 string `json:"sha256"`
|
|
|
|
|
TargetPartition string `json:"target_partition,omitempty"`
|
|
|
|
|
Encoding string `json:"encoding,omitempty"`
|
|
|
|
|
ExpandedSize int64 `json:"expanded_size,omitempty"`
|
|
|
|
|
ExpandedSHA256 string `json:"expanded_sha256,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Manifest struct {
|
|
|
|
|
FormatVersion int `json:"format_version"`
|
|
|
|
|
PackageID string `json:"package_id"`
|
|
|
|
|
Product string `json:"product"`
|
|
|
|
|
HardwareProfile string `json:"hardware_profile"`
|
|
|
|
|
Version string `json:"version"`
|
|
|
|
|
MinimumCurrentVersion string `json:"minimum_current_version,omitempty"`
|
|
|
|
|
AllowDowngrade bool `json:"allow_downgrade"`
|
|
|
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
|
SigningKeyID string `json:"signing_key_id"`
|
|
|
|
|
Payloads []Payload `json:"payloads"`
|
|
|
|
|
Apply struct {
|
|
|
|
|
Capability string `json:"capability"`
|
|
|
|
|
RebootRequired bool `json:"reboot_required"`
|
|
|
|
|
} `json:"apply"`
|
|
|
|
|
Recovery struct {
|
|
|
|
|
Capability string `json:"capability"`
|
|
|
|
|
Instructions string `json:"instructions"`
|
|
|
|
|
} `json:"recovery"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Status struct {
|
|
|
|
|
FormatVersion int `json:"format_version"`
|
|
|
|
|
OperationID string `json:"operation_id,omitempty"`
|
|
|
|
|
State string `json:"state"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
Filename string `json:"filename,omitempty"`
|
|
|
|
|
DeviceVersion string `json:"device_version,omitempty"`
|
|
|
|
|
BytesReceived int64 `json:"bytes_received"`
|
|
|
|
|
BytesTotal int64 `json:"bytes_total"`
|
|
|
|
|
ProgressPercent int `json:"progress_percent"`
|
|
|
|
|
PackagePath string `json:"-"`
|
|
|
|
|
Manifest *Manifest `json:"manifest,omitempty"`
|
|
|
|
|
SignerKeyID string `json:"signer_key_id,omitempty"`
|
|
|
|
|
// ApplyPolicy is persisted by field firmware; retain it across upgrades.
|
|
|
|
|
ApplyPolicy string `json:"apply_policy,omitempty"`
|
|
|
|
|
ApplyEnabled bool `json:"apply_enabled"`
|
|
|
|
|
ApplyDisabledReason string `json:"apply_disabled_reason,omitempty"`
|
|
|
|
|
RecoveryInstructions string `json:"recovery_instructions,omitempty"`
|
|
|
|
|
FailureCode string `json:"failure_code,omitempty"`
|
|
|
|
|
FailureReason string `json:"failure_reason,omitempty"`
|
|
|
|
|
RecoveryStep string `json:"recovery_step,omitempty"`
|
|
|
|
|
RecoveryAttempt int `json:"recovery_attempt,omitempty"`
|
|
|
|
|
Capacity capacity.Snapshot `json:"capacity"`
|
|
|
|
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
|
|
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type UploadRequest struct {
|
|
|
|
|
Filename string
|
|
|
|
|
ContentLength int64
|
|
|
|
|
Actor string
|
|
|
|
|
Body io.Reader
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ApplyRequest struct {
|
|
|
|
|
Actor string
|
|
|
|
|
OperationID string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Controller interface {
|
|
|
|
|
Status(context.Context) (Status, error)
|
|
|
|
|
Upload(context.Context, UploadRequest) (Status, error)
|
|
|
|
|
Apply(context.Context, ApplyRequest) (Status, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Error struct {
|
|
|
|
|
Code string
|
|
|
|
|
Message string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Error) Error() string { return e.Message }
|
|
|
|
|
|
|
|
|
|
func Code(err error) string {
|
|
|
|
|
var updateErr *Error
|
|
|
|
|
if errors.As(err, &updateErr) {
|
|
|
|
|
return updateErr.Code
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type UnavailableController struct{ Reason string }
|
|
|
|
|
|
|
|
|
|
func (u UnavailableController) unavailable() error {
|
|
|
|
|
reason := u.Reason
|
|
|
|
|
if reason == "" {
|
|
|
|
|
reason = "tera-updated is unavailable"
|
|
|
|
|
}
|
|
|
|
|
return &Error{Code: "update_unavailable", Message: reason}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u UnavailableController) Status(context.Context) (Status, error) {
|
|
|
|
|
return Status{}, u.unavailable()
|
|
|
|
|
}
|
|
|
|
|
func (u UnavailableController) Upload(context.Context, UploadRequest) (Status, error) {
|
|
|
|
|
return Status{}, u.unavailable()
|
|
|
|
|
}
|
|
|
|
|
func (u UnavailableController) Apply(context.Context, ApplyRequest) (Status, error) {
|
|
|
|
|
return Status{}, u.unavailable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FactoryResetter interface{ FactoryReset(context.Context) error }
|