package main import ( "bytes" "crypto/ecdh" "crypto/rand" "encoding/base64" "encoding/json" "os" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/overlay" ) // Issue 1593 (novox/hq ADR 0468 §8): `shares` on a requirement of s3-bucket, taken in with mesh-sdk 465beb4. The fixture's // shape is mesh-sdk's go/catalogue/bucket_shares_test.go (issue 1591) and go/compose/resolve_test.go theObjectsMesh. const filesRequiringShares = `{"module":"files","version":"1","requires":[ {"name":"s3-bucket","shares":[{"to":"seat:node-files","methods":["put"]}]}]}` func aKey(t *testing.T) string { t.Helper() k, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } return base64.StdEncoding.EncodeToString(k.PublicKey().Bytes()) } // 1593/AC1 unit: composing the provider's machine, the file it receives for the consumer carries the shares. func TestTheProvidersMachineReceivesTheSharesOfAnS3BucketRequirement(t *testing.T) { var overlayModule catalogue.Manifest raw, err := json.Marshal(overlay.Manifest()) if err != nil { t.Fatal(err) } if err := json.Unmarshal(raw, &overlayModule); err != nil { t.Fatal(err) } consumer, err := catalogue.ParseManifest([]byte(filesRequiringShares)) if err != nil { t.Fatal(err) } provider := catalogue.Manifest{Module: "objects", Version: "1", Provides: []catalogue.Offer{{Name: "s3-bucket", Scope: catalogue.ScopeMesh, Identity: &catalogue.OfferIdentity{Max: 20, In: "an S3 access key"}}}, Receives: map[string]string{"s3-bucket": "/var/lib/mesh/objects/mesh.json"}} in := compose.Input{ Catalogue: map[string]catalogue.Manifest{overlay.Name: overlayModule, "objects": provider, "files": consumer}, Issued: []compose.Issued{{Provision: "s3-bucket", Consumer: "laptop", ConsumerModule: "files", Provider: "anchor", ForProvider: "sealed-to-anchor"}}, } for i, name := range []string{"anchor", "laptop"} { assigned := []string{overlay.Name, "objects"} if name == "laptop" { assigned = []string{overlay.Name, "files"} } in.Nodes = append(in.Nodes, compose.Node{Name: name, SealingKey: name + "-key", Assigned: assigned, Place: compose.Place{Key: aKey(t), Endpoint: name + ".example:51820", Site: "here", Hub: name == "anchor", Address: []string{"10.77.0.1", "10.77.0.2"}[i]}, Capabilities: map[string]bool{"container-runtime": true, "wireguard": true, "systemd": true}}) } rendered, err := compose.Render(in, "anchor", compose.Foreseeing) if err != nil { t.Fatal(err) } if len(rendered.Rendering.Grants) != 1 { t.Fatalf("grants are %+v", rendered.Rendering.Grants) } got, err := json.Marshal(rendered.Rendering.Grants[0].Values["shares"]) if err != nil { t.Fatal(err) } if want := `[{"methods":["put"],"to":"seat:node-files"}]`; string(got) != want { t.Fatalf("the provider's grant for the consumer carries shares %s, want %s", got, want) } } // 1593/AC2 unit: the module check accepts a manifest with shares and refuses an unknown method, citing ADR 0468 §8. func TestModuleCheckAcceptsSharesAndRefusesAnUnknownMethod(t *testing.T) { dir := t.TempDir() write := func(name, body string) string { p := filepath.Join(dir, name+".json") if err := os.WriteFile(p, []byte(body), 0o600); err != nil { t.Fatal(err) } return p } var out bytes.Buffer if err := moduleCheckFor([]string{write("files", filesRequiringShares)}, 6, &out); err != nil { t.Fatalf("a manifest with shares was refused: %v\n%s", err, out.String()) } out.Reset() bad := write("bad", `{"module":"bad","version":"1","requires":[ {"name":"s3-bucket","shares":[{"to":"seat:node-files","methods":["delete"]}]}]}`) err := moduleCheckFor([]string{bad}, 6, &out) if err == nil || !strings.Contains(out.String(), "ADR 0468 §8") { t.Fatalf("an unknown method passed or the refusal does not cite the ADR: %v\n%s", err, out.String()) } }