Files
go-sip/cmd/sip-go-agent/upload_retry.go
T

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)
}