Files
mesh-sdk/go/delivery/fingerprint2.go
T

106 lines
4.1 KiB
Go

package delivery
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
)
// SourcePrefix2 opens every source fingerprint of the new form.
const SourcePrefix2 = "src2:"
// Toolchain2 is what an artifact's toolchain enters the new fingerprint as (novox/hq ADR 0405 clause 7): the
// artifact's language, the installed toolchain's version and the build module's recipe version, in place of the
// toolchain image's digest. A toolchain update so changes the fingerprints of that language's modules only.
type Toolchain2 struct {
Language string
Version string
Recipe string
}
// SourceInputs2 are what a module's source fingerprint of the new form is of (novox/hq ADR 0429 clause 5 as ADR 0437
// clause 5 changed it; ADR 0453 clause 6). It differs from SourceInputs in exactly four things: a base enters by its
// module's fingerprint (src2:...), not its image digest; the builder enters by its own module's fingerprint, not its
// executable's digest; an artifact that resolves packages when it is built enters by the hash of its lock file; and
// an artifact's toolchain enters as Toolchain2. Module, Tree, Contexts and Unpinned are as in SourceInputs.
//
// Locks names the artifacts that resolve packages when built, each with the hash of its lock file; an entry with an
// empty hash is such an artifact with no lock, which has no fingerprint. An artifact that resolves nothing is not in
// Locks.
type SourceInputs2 struct {
Module string
Tree string
Bases []string
Builder string
Contexts map[string]string
Locks map[string]string
Toolchains map[string]Toolchain2
Unpinned string
}
// SourceFingerprint2 is the module's source fingerprint of the new form, `src2:` and the hex sha256 of its inputs'
// lines, or none and the reason when the source does not pin the build: Unpinned when set, "no tree" when Tree is
// empty, "no lock" when an artifact resolves packages and has no lock. It is a pure function of its inputs: the order
// of maps and a base given twice change nothing. The lines are "module <m>", "tree <t>" and "builder <fp>", then
// "base <fp>" (once each), "context <artifact> <tree>", "lock <artifact> <hash>" and "toolchain <artifact> <language>
// <version> <recipe>", sorted, all joined by a newline.
func SourceFingerprint2(in SourceInputs2) (fp, none string) {
if in.Unpinned != "" {
return "", in.Unpinned
}
if in.Tree == "" {
return "", "no tree"
}
for _, h := range in.Locks {
if h == "" {
return "", "no lock"
}
}
lines := []string{"module " + in.Module, "tree " + in.Tree, "builder " + in.Builder}
bases := map[string]bool{}
for _, b := range in.Bases {
bases[b] = true
}
for b := range bases {
lines = append(lines, "base "+b)
}
for a, t := range in.Contexts {
lines = append(lines, "context "+a+" "+t)
}
for a, h := range in.Locks {
lines = append(lines, "lock "+a+" "+h)
}
for a, t := range in.Toolchains {
lines = append(lines, "toolchain "+a+" "+t.Language+" "+t.Version+" "+t.Recipe)
}
sort.Strings(lines[3:])
sum := sha256.Sum256([]byte(strings.Join(lines, "\n")))
return SourcePrefix2 + hex.EncodeToString(sum[:]), ""
}
// FingerprintKeyPart is a new-form fingerprint as a key part: its 64 hex digits without the prefix, since a bus key
// may not hold ':'. It refuses a value that is not `src2:` and 64 lowercase hex digits, a src1 fingerprint included,
// naming it.
func FingerprintKeyPart(fp string) (string, error) {
part, ok := strings.CutPrefix(fp, SourcePrefix2)
if !ok || !isHex64(part) {
return "", fmt.Errorf("delivery: %q is not a fingerprint of the new form, %s and 64 lowercase hex digits", fp, SourcePrefix2)
}
return part, nil
}
// ParseFingerprintKeyPart is the inverse of FingerprintKeyPart. It refuses a part that is not 64 lowercase hex
// digits, a src1 fingerprint or a prefixed value included, naming it.
func ParseFingerprintKeyPart(part string) (string, error) {
if !isHex64(part) {
return "", fmt.Errorf("delivery: %q is not the key part of a fingerprint, 64 lowercase hex digits", part)
}
return SourcePrefix2 + part, nil
}
func isHex64(s string) bool {
return len(s) == 64 && strings.Trim(s, "0123456789abcdef") == ""
}