Merge pull request 'One rule for whether a plan hands its module, so no part reads a provisions-only plan as handed (issue 774)' (#574) from fix/plan-hands into main
This commit was merged in pull request #574.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
module checks/planhands
|
||||||
|
|
||||||
|
go 1.26.0
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Package planhands refuses a module's line that takes a plan existing for its module handed to the provisioner (issue
|
||||||
|
// 774). Whether a plan hands its module is mesh-sdk's one rule, catalogue.Plan.Hands: since the seat mesh-provisioner keys a
|
||||||
|
// provision its provider's helper answers for a module not handed, a plan naming provisions alone exists for modules
|
||||||
|
// whose files and own secrets stay their node's (on 2026-10-30 the vault read every such plan as handed and answered
|
||||||
|
// twenty modules' own secrets to nobody, and the provisioner client refused each as a plan it could not act on). A line
|
||||||
|
// that tests whether a plan exists either asks Hands on the same line, or says "not handing": it reads only who binds
|
||||||
|
// whom, or that there is no plan at all.
|
||||||
|
package planhands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// existence is a test of whether an assignment's or a plan's `provisioning` is there: nil, or an empty raw value.
|
||||||
|
var existence = regexp.MustCompile(`Provisioning\s*[!=]=\s*nil|len\([^)]*Provisioning\)\s*[!=]=\s*0`)
|
||||||
|
|
||||||
|
// Problems is each such line under root's modules/ that neither asks Hands nor says "not handing", as
|
||||||
|
// `path:line: text`, sorted. Test files are left out: a test may build a plan by hand.
|
||||||
|
func Problems(root string) ([]string, error) {
|
||||||
|
var out []string
|
||||||
|
err := filepath.WalkDir(filepath.Join(root, "modules"), func(p string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() || !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(root, p)
|
||||||
|
for i, line := range strings.Split(string(raw), "\n") {
|
||||||
|
if existence.MatchString(line) && !strings.Contains(line, ".Hands()") && !strings.Contains(line, "not handing") {
|
||||||
|
out = append(out, fmt.Sprintf("%s:%d: %s", rel, i+1, strings.TrimSpace(line)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
sort.Strings(out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package planhands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A line that takes a plan existing for its module handed is refused and named; one that asks Hands, one marked "not
|
||||||
|
// handing" and any test file pass; and the real catalogue is clean.
|
||||||
|
func TestNoModuleTakesAPlanExistingForItsModuleHanded(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
for f, body := range map[string]string{
|
||||||
|
"modules/vault/own.go": "if a.Provisioning != nil {\n",
|
||||||
|
"modules/client/plan.go": "if len(a.Provisioning) == 0 {\n",
|
||||||
|
"modules/vault/rule.go": "return a.Provisioning != nil && a.Provisioning.Hands(), nil\n",
|
||||||
|
"modules/client/binds.go": "if a.Provisioning != nil { // binds, not handing\n",
|
||||||
|
"modules/vault/own_test.go": "if a.Provisioning == nil {\n",
|
||||||
|
"modules/client/notgo/plan.md": "if a.Provisioning == nil {\n",
|
||||||
|
} {
|
||||||
|
p := filepath.Join(root, f)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got, err := Problems(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(got) != 2 || !strings.HasPrefix(got[0], "modules/client/plan.go:1:") || !strings.HasPrefix(got[1], "modules/vault/own.go:1:") {
|
||||||
|
t.Fatalf("refused %v, wanted the client's and the vault's existence tests alone", got)
|
||||||
|
}
|
||||||
|
real, err := Problems(filepath.Join("..", ".."))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(real) > 0 {
|
||||||
|
t.Errorf("a module takes a plan existing for its module handed; ask catalogue.Plan.Hands, or mark a line that reads "+
|
||||||
|
"only who binds whom \"not handing\":\n%s", strings.Join(real, "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# What these tests read outside their package, as paths from the repository's root (issue 1936).
|
||||||
|
# every module whole (a directory is read whole, so a module's Go source at any depth enters the digest), for a line that takes a plan existing for its module handed.
|
||||||
|
modules
|
||||||
@@ -870,4 +870,11 @@ func TestAPlanOfProvisionsAloneFetchesNoTemplates(t *testing.T) {
|
|||||||
if targets, wrong := fillTargets(Plan{}, texts); wrong != "" || len(targets) != 0 {
|
if targets, wrong := fillTargets(Plan{}, texts); wrong != "" || len(targets) != 0 {
|
||||||
t.Fatalf("filled %v: %s", targets, wrong)
|
t.Fatalf("filled %v: %s", targets, wrong)
|
||||||
}
|
}
|
||||||
|
// And such a plan hands its module to nobody (catalogue.Plan.Hands): read as no plan, never refused as one that
|
||||||
|
// names no source, and the module's files stay its node's.
|
||||||
|
routeOnly := `{"node":"g14","module":"grafana","provisioning":{"templates":{"digest":"","source":""},"files":null,` +
|
||||||
|
`"provisions":[{"provision":"route","provider":"g14","provider-module":"route-proxy"}]}}`
|
||||||
|
if module, _, err := planOf("g14", "g14.grafana", []byte(routeOnly)); module != "grafana" || !errors.Is(err, errNoPlan) {
|
||||||
|
t.Fatalf("a plan of provisions alone reads as %s: %v, want no plan", module, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func takeBinds(s *state, key string, value []byte, gone bool) bool {
|
|||||||
return was != nil
|
return was != nil
|
||||||
}
|
}
|
||||||
b := binding{node: a.Node, module: a.Module}
|
b := binding{node: a.Node, module: a.Module}
|
||||||
if a.Provisioning != nil {
|
if a.Provisioning != nil { // binds, not handing: a plan of provisions alone still binds its consumer
|
||||||
b.provisions = a.Provisioning.Provisions
|
b.provisions = a.Provisioning.Provisions
|
||||||
}
|
}
|
||||||
s.binds[key] = b
|
s.binds[key] = b
|
||||||
|
|||||||
@@ -178,7 +178,14 @@ func planOf(node, key string, value []byte) (string, Plan, error) {
|
|||||||
return "", Plan{}, fmt.Errorf("the assignment %s names node %q and module %q, not its key's", key, a.Node,
|
return "", Plan{}, fmt.Errorf("the assignment %s names node %q and module %q, not its key's", key, a.Node,
|
||||||
a.Module)
|
a.Module)
|
||||||
}
|
}
|
||||||
if len(a.Provisioning) == 0 || string(a.Provisioning) == "null" {
|
if len(a.Provisioning) == 0 || string(a.Provisioning) == "null" { // no plan at all, not handing
|
||||||
|
return a.Module, Plan{}, errNoPlan
|
||||||
|
}
|
||||||
|
// **A plan that hands nothing is no plan of this client's** (mesh-sdk catalogue.Plan.Hands, issue 774): the seat keys
|
||||||
|
// a provision its provider's helper answers for a module not handed (grafana's route), and that plan names
|
||||||
|
// provisions alone. Read leniently for the rule; a plan that hands is read strictly below.
|
||||||
|
var rule catalogue.Plan
|
||||||
|
if err := json.Unmarshal(a.Provisioning, &rule); err == nil && !rule.Hands() {
|
||||||
return a.Module, Plan{}, errNoPlan
|
return a.Module, Plan{}, errNoPlan
|
||||||
}
|
}
|
||||||
var p Plan
|
var p Plan
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-provisioner-client
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030150314-9379e9a96ad4
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.48.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030150314-9379e9a96ad4 h1:CPUqC3FS/ezvBYdhVgVdsQg2DdN8xgtROQkSaHRKeOI=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115 h1:ezKg8aAKQoZ5XEAWQQVk4y/x0h2qC73GcUKBmbqY5jw=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030150314-9379e9a96ad4/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ func (d Declared) May(caller, name string) (bool, string) {
|
|||||||
if err := json.Unmarshal(raw, &a); err != nil || a.Node != node || a.Module != module {
|
if err := json.Unmarshal(raw, &a); err != nil || a.Node != node || a.Module != module {
|
||||||
return false, fmt.Sprintf("the assignment %s.%s is not the controller's", node, module)
|
return false, fmt.Sprintf("the assignment %s.%s is not the controller's", node, module)
|
||||||
}
|
}
|
||||||
if a.Provisioning == nil {
|
if !hands(raw) {
|
||||||
return false, fmt.Sprintf("%s on %s is not handed to the provisioner, so its secrets reach it in its "+
|
return false, fmt.Sprintf("%s on %s is not handed to the provisioner, so its secrets reach it in its "+
|
||||||
"declaration (novox/hq ADR 0349 §17)", module, node)
|
"declaration (novox/hq ADR 0349 §17)", module, node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (v *Vault) outcomes() (map[string]BoundOutcome, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var a plannedProvisions
|
var a plannedProvisions
|
||||||
if err := json.Unmarshal(raw, &a); err != nil || a.Provisioning == nil {
|
if err := json.Unmarshal(raw, &a); err != nil || a.Provisioning == nil { // binds, not handing
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
consumer := provisions.End{Module: a.Module, Node: a.Node}
|
consumer := provisions.End{Module: a.Module, Node: a.Node}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -348,23 +349,30 @@ func (p *planReader) handed(module string) (bool, error) {
|
|||||||
if raw == nil {
|
if raw == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
var a struct {
|
hands, err := handsOf(raw)
|
||||||
Provisioning *struct {
|
if err != nil {
|
||||||
Templates struct {
|
|
||||||
Source string `json:"source"`
|
|
||||||
} `json:"templates"`
|
|
||||||
Files []json.RawMessage `json:"files"`
|
|
||||||
Secrets []json.RawMessage `json:"secrets"`
|
|
||||||
Items []json.RawMessage `json:"items"`
|
|
||||||
} `json:"provisioning"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(raw, &a); err != nil {
|
|
||||||
return false, fmt.Errorf("the assignment %s.%s is not the controller's: %w", node, module, err)
|
return false, fmt.Errorf("the assignment %s.%s is not the controller's: %w", node, module, err)
|
||||||
}
|
}
|
||||||
// **A plan that names provisions alone hands nothing** (catalogue.Keyed, issue 774): the seat keys a provision its
|
return hands, nil
|
||||||
// provider's helper answers for a module not handed (grafana's route), and that module's own secrets are still
|
}
|
||||||
// composed by its node-engine from this answer. Handed is a plan that names something the client fills.
|
|
||||||
hands := a.Provisioning
|
// handsOf is whether an assignment's value carries a plan that hands its module to the provisioner: mesh-sdk's one
|
||||||
return hands != nil && (hands.Templates.Source != "" || len(hands.Files) > 0 || len(hands.Secrets) > 0 ||
|
// rule, catalogue.Plan.Hands (issue 774). A plan naming provisions alone (a route the seat keys for a module not
|
||||||
len(hands.Items) > 0), nil
|
// handed) hands nothing, and that module's files and own secrets stay its node's composition. Whether a plan exists
|
||||||
|
// is never the rule; a reader that asks only who binds whom reads the provisions and says so ("not handing", which
|
||||||
|
// the catalogue check checks/planhands holds every module to).
|
||||||
|
func handsOf(raw []byte) (bool, error) {
|
||||||
|
var a struct {
|
||||||
|
Provisioning *catalogue.Plan `json:"provisioning"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &a); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return a.Provisioning != nil && a.Provisioning.Hands(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hands is handsOf for a value already read as the controller's: one that cannot be read hands nothing.
|
||||||
|
func hands(raw []byte) bool {
|
||||||
|
h, err := handsOf(raw)
|
||||||
|
return err == nil && h
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,10 +242,10 @@ func readBindings(m Mirror) (bindings, error) {
|
|||||||
return b, fmt.Errorf("the assignment %s is not the controller's", key)
|
return b, fmt.Errorf("the assignment %s is not the controller's", key)
|
||||||
}
|
}
|
||||||
b.assigned[key] = true
|
b.assigned[key] = true
|
||||||
if a.Provisioning == nil {
|
if a.Provisioning == nil { // binds, not handing: a plan of provisions alone still binds its consumer
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.handed[key] = true
|
b.handed[key] = hands(raw)
|
||||||
for _, p := range a.Provisioning.Provisions {
|
for _, p := range a.Provisioning.Provisions {
|
||||||
if !namePart.MatchString(p.Provider) || !namePart.MatchString(p.Provision) {
|
if !namePart.MatchString(p.Provider) || !namePart.MatchString(p.Provision) {
|
||||||
continue
|
continue
|
||||||
@@ -713,7 +713,7 @@ func sharedRefusal(store *Store, read AssignmentReader, node, provider, provisio
|
|||||||
for _, m := range strings.Split(sh.ProviderModule, ",") {
|
for _, m := range strings.Split(sh.ProviderModule, ",") {
|
||||||
if raw, err := read(node + "." + m); err == nil && raw != nil {
|
if raw, err := read(node + "." + m); err == nil && raw != nil {
|
||||||
var a plannedProvisions
|
var a plannedProvisions
|
||||||
if json.Unmarshal(raw, &a) == nil && a.Node == node && a.Module == m && a.Provisioning != nil {
|
if json.Unmarshal(raw, &a) == nil && a.Node == node && a.Module == m && hands(raw) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -729,7 +729,7 @@ func sharedRefusal(store *Store, read AssignmentReader, node, provider, provisio
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var a plannedProvisions
|
var a plannedProvisions
|
||||||
if json.Unmarshal(raw, &a) != nil || a.Node != node || a.Module != parts[1] || a.Provisioning == nil {
|
if json.Unmarshal(raw, &a) != nil || a.Node != node || a.Module != parts[1] || !hands(raw) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, p := range a.Provisioning.Provisions {
|
for _, p := range a.Provisioning.Provisions {
|
||||||
@@ -811,7 +811,7 @@ func pairRowRefusal(read AssignmentReader, node string, parts []string) string {
|
|||||||
if json.Unmarshal(raw, &a) != nil || a.Node != consumer || a.Module != module {
|
if json.Unmarshal(raw, &a) != nil || a.Node != consumer || a.Module != module {
|
||||||
return fmt.Sprintf("the assignment %s.%s is not the controller's", consumer, module)
|
return fmt.Sprintf("the assignment %s.%s is not the controller's", consumer, module)
|
||||||
}
|
}
|
||||||
if a.Provisioning == nil {
|
if !hands(raw) {
|
||||||
return fmt.Sprintf("%s on %s is not handed to the provisioner, so its login reaches it in its declaration "+
|
return fmt.Sprintf("%s on %s is not handed to the provisioner, so its login reaches it in its declaration "+
|
||||||
"(ADR 0349 clause 17)", module, consumer)
|
"(ADR 0349 clause 17)", module, consumer)
|
||||||
}
|
}
|
||||||
@@ -827,7 +827,7 @@ func pairRowRefusal(read AssignmentReader, node string, parts []string) string {
|
|||||||
if p.ProviderModule != "" {
|
if p.ProviderModule != "" {
|
||||||
if praw, err := read(provider + "." + p.ProviderModule); err == nil && praw != nil {
|
if praw, err := read(provider + "." + p.ProviderModule); err == nil && praw != nil {
|
||||||
var pa plannedProvisions
|
var pa plannedProvisions
|
||||||
if json.Unmarshal(praw, &pa) == nil && pa.Node == provider && pa.Provisioning != nil {
|
if json.Unmarshal(praw, &pa) == nil && pa.Node == provider && hands(praw) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module mesh-vault
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030174137-a37181261c24
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030174137-a37181261c24 h1:s0XXZtR7CFPBbnaDygWnE4/Q827iEFt0YfEzujk4Q9g=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115 h1:ezKg8aAKQoZ5XEAWQQVk4y/x0h2qC73GcUKBmbqY5jw=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030174137-a37181261c24/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
Reference in New Issue
Block a user