Preserve exact evidence bytes in private publication records
This commit is contained in:
@@ -12,15 +12,18 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.maximumdirect.net/releasekit/internal/git"
|
||||
)
|
||||
|
||||
const recordFormatVersion = 1
|
||||
const recordByteFormatVersion = 2
|
||||
|
||||
type recordEnvelope struct {
|
||||
FormatVersion int `json:"format_version"`
|
||||
Evidence json.RawMessage `json:"evidence"`
|
||||
EvidenceBytes json.RawMessage `json:"evidence_bytes,omitempty"`
|
||||
}
|
||||
|
||||
// LoadEvidence reads one complete supported private publication record.
|
||||
@@ -38,7 +41,7 @@ func LoadEvidence(path string) (Evidence, error) {
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("record %q contains trailing JSON", path)}
|
||||
}
|
||||
if envelope.FormatVersion != recordFormatVersion {
|
||||
if envelope.FormatVersion != recordFormatVersion && envelope.FormatVersion != recordByteFormatVersion {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("record %q has unsupported format version %d", path, envelope.FormatVersion)}
|
||||
}
|
||||
if len(envelope.Evidence) == 0 || string(envelope.Evidence) == "null" {
|
||||
@@ -48,9 +51,65 @@ func LoadEvidence(path string) (Evidence, error) {
|
||||
if err := json.Unmarshal(envelope.Evidence, &evidence); err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("decode record evidence %q: %w", path, err)}
|
||||
}
|
||||
if envelope.FormatVersion == recordByteFormatVersion && len(envelope.EvidenceBytes) > 0 {
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(envelope.EvidenceBytes, &fields); err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("decode record evidence bytes %q: %w", path, err)}
|
||||
}
|
||||
for _, field := range evidenceFields(&evidence) {
|
||||
if raw, ok := fields[field.name]; ok {
|
||||
var bytes []byte
|
||||
if err := json.Unmarshal(raw, &bytes); err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("decode record field %s bytes %q: %w", field.name, path, err)}
|
||||
}
|
||||
*field.value = string(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
return evidence, nil
|
||||
}
|
||||
|
||||
type evidenceField struct {
|
||||
name string
|
||||
value *string
|
||||
}
|
||||
|
||||
func evidenceFields(e *Evidence) [10]evidenceField {
|
||||
return [10]evidenceField{
|
||||
{"repository_dir", &e.RepositoryDir}, {"module_dir", &e.ModuleDir},
|
||||
{"module_path", &e.ModulePath}, {"version", &e.Version}, {"tag", &e.Tag},
|
||||
{"candidate_commit", &e.CandidateCommit}, {"tag_object", &e.TagObject},
|
||||
{"endpoint", &e.Endpoint}, {"remote", &e.Remote}, {"recorded_at", &e.RecordedAt},
|
||||
}
|
||||
}
|
||||
|
||||
func encodeRecord(evidence Evidence) ([]byte, error) {
|
||||
envelope := recordEnvelope{FormatVersion: recordFormatVersion}
|
||||
var bytes map[string][]byte
|
||||
for _, field := range evidenceFields(&evidence) {
|
||||
if !utf8.ValidString(*field.value) {
|
||||
if bytes == nil {
|
||||
bytes = make(map[string][]byte)
|
||||
}
|
||||
bytes[field.name] = []byte(*field.value)
|
||||
*field.value = ""
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if bytes != nil {
|
||||
envelope.FormatVersion = recordByteFormatVersion
|
||||
envelope.EvidenceBytes, err = json.Marshal(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
envelope.Evidence, err = json.Marshal(evidence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(envelope)
|
||||
}
|
||||
|
||||
// SaveEvidence writes one private attempt record and returns its absolute path.
|
||||
func SaveEvidence(ctx context.Context, repositoryDir, override string, evidence Evidence) (string, error) {
|
||||
recordDir, root, err := resolveRecordDir(ctx, repositoryDir, override)
|
||||
@@ -71,11 +130,7 @@ func saveEvidenceNamed(ctx context.Context, repositoryDir, root, override, recor
|
||||
if err := os.MkdirAll(recordDir, 0o700); err != nil {
|
||||
return "", &RecoveryError{Kind: "record_write", cause: fmt.Errorf("create record directory %q: %w", recordDir, err)}
|
||||
}
|
||||
evidenceJSON, err := json.Marshal(evidence)
|
||||
if err != nil {
|
||||
return "", &RecoveryError{Kind: "record_write", cause: fmt.Errorf("encode publication evidence: %w", err)}
|
||||
}
|
||||
data, err := json.Marshal(recordEnvelope{FormatVersion: recordFormatVersion, Evidence: evidenceJSON})
|
||||
data, err := encodeRecord(evidence)
|
||||
if err != nil {
|
||||
return "", &RecoveryError{Kind: "record_write", cause: fmt.Errorf("encode publication evidence: %w", err)}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user