84 lines
3.4 KiB
Go
84 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"net/url"
|
|
"strings"
|
|
|
|
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
|
|
"git.ipao.vip/rogee/go-sip/internal/agent"
|
|
"git.ipao.vip/rogee/go-sip/internal/config"
|
|
"github.com/google/uuid"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// retryUploadRecording is reachable only through an explicit operator command.
|
|
// A supplied request ID is stable across redelivery and can authorize one PUT.
|
|
func retryUploadRecording(ctx context.Context, cfg config.Config, client recordingUploadRPC, uploader agent.UploadClient, spool *agent.Spool, uploadID, requestID, path string) (returned agent.UploadAttempt, returnErr error) {
|
|
parsedID, err := uuid.Parse(requestID)
|
|
if err != nil || parsedID.Version() != 4 || parsedID.Variant() != uuid.RFC4122 || parsedID.String() != requestID {
|
|
return returned, errors.New("explicit request ID must be a canonical UUID v4")
|
|
}
|
|
lock, err := spool.LockUpload(uploadID)
|
|
if err != nil {
|
|
return returned, err
|
|
}
|
|
defer func() { returnErr = errors.Join(returnErr, lock.Close()) }()
|
|
record, err := spool.LoadUploadAttempt(uploadID)
|
|
if err != nil {
|
|
return record, err
|
|
}
|
|
if record.State != "attempted" || record.Binding == nil || record.Asset == nil || record.RequestID == "" || record.RequestID == requestID {
|
|
return record, errors.New("only an unsuccessful upload may explicitly request a new grant")
|
|
}
|
|
raw, err := (proto.MarshalOptions{Deterministic: true}).Marshal(&agentpb.RequestUploadRequest{Binding: record.Binding, Asset: record.Asset, UploadId: uploadID})
|
|
if err != nil {
|
|
return record, err
|
|
}
|
|
sum := sha256.Sum256(raw)
|
|
if record.Identity != hex.EncodeToString(sum[:]) || stableUploadID(record.Binding, record.Asset) != uploadID {
|
|
return record, errors.New("persisted upload identity mismatch")
|
|
}
|
|
meta := uploadMeta(cfg, "request", uploadID)
|
|
meta.RequestId = requestID
|
|
meta.OperationId = requestID
|
|
meta.IdempotencyKey = requestID
|
|
meta.TraceId = requestID
|
|
response, err := client.RequestUpload(ctx, &agentpb.RequestUploadRequest{Meta: meta, Binding: record.Binding, Asset: record.Asset, UploadId: uploadID})
|
|
if err != nil {
|
|
return record, err
|
|
}
|
|
if response.GetReceipt().GetResult() != agentpb.ResultCode_RESULT_CODE_ACCEPTED || response.GetGrant() == nil {
|
|
return record, errors.New("explicit upload request was not granted")
|
|
}
|
|
grant := response.Grant
|
|
if grant.UploadId != uploadID || grant.ObjectKey != record.ObjectKey || grant.RequiredChecksumSha256 != record.Asset.ChecksumSha256 || grant.MaxBytes < record.Asset.SizeBytes {
|
|
return record, errors.New("replacement grant does not match the original asset")
|
|
}
|
|
target, err := url.Parse(grant.TargetUrl)
|
|
if err != nil || target.Host == "" {
|
|
return record, errors.New("replacement grant has invalid target")
|
|
}
|
|
if err := spool.ReserveUploadRetry(uploadID, requestID); err != nil {
|
|
return record, err
|
|
}
|
|
uploader.AllowedHosts = map[string]struct{}{strings.ToLower(target.Host): {}}
|
|
result, err := uploader.UploadFile(ctx, grant, path)
|
|
if err != nil {
|
|
return record, err
|
|
}
|
|
if result.SizeBytes != record.Asset.SizeBytes || result.SHA256 != record.Asset.ChecksumSha256 {
|
|
return record, errors.New("replacement upload result does not match original asset")
|
|
}
|
|
if err := spool.RecordUploadResult(uploadID, result); err != nil {
|
|
return record, err
|
|
}
|
|
record.RequestID = requestID
|
|
record.State = "uploaded"
|
|
record.Result = result
|
|
return notifyUploadedRecordingLocked(ctx, cfg, client, spool, record)
|
|
}
|