-
Notifications
You must be signed in to change notification settings - Fork 245
/
loader.go
149 lines (123 loc) · 4.79 KB
/
loader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package validationfile
import (
"context"
"fmt"
"os"
core "github.com/authzed/spicedb/pkg/proto/core/v1"
v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/rs/zerolog/log"
"github.com/shopspring/decimal"
"google.golang.org/protobuf/encoding/prototext"
"github.com/authzed/spicedb/internal/datastore"
dsctx "github.com/authzed/spicedb/internal/middleware/datastore"
"github.com/authzed/spicedb/internal/namespace"
"github.com/authzed/spicedb/pkg/tuple"
)
// PopulatedValidationFile contains the fully parsed information from a validation file.
type PopulatedValidationFile struct {
// Schema is the entered schema text, if any.
Schema string
// NamespaceDefinitions are the namespaces defined in the validation file, in either
// direct or compiled from schema form.
NamespaceDefinitions []*core.NamespaceDefinition
// Tuples are the relation tuples defined in the validation file, either directly
// or in the relationships block.
Tuples []*core.RelationTuple
// ParsedFiles are the underlying parsed validation files.
ParsedFiles []ValidationFile
}
// PopulateFromFiles populates the given datastore with the namespaces and tuples found in
// the validation file(s) specified.
func PopulateFromFiles(ds datastore.Datastore, filePaths []string) (*PopulatedValidationFile, decimal.Decimal, error) {
var revision decimal.Decimal
nsDefs := []*core.NamespaceDefinition{}
schema := ""
tuples := []*core.RelationTuple{}
files := []ValidationFile{}
for _, filePath := range filePaths {
fileContents, err := os.ReadFile(filePath)
if err != nil {
return nil, decimal.Zero, err
}
parsed, err := DecodeValidationFile(fileContents)
if err != nil {
return nil, decimal.Zero, fmt.Errorf("error when parsing config file %s: %w", filePath, err)
}
files = append(files, *parsed)
// Add schema-based namespace definitions.
defs := parsed.Schema.Definitions
if len(defs) > 0 {
schema += parsed.Schema.Schema + "\n\n"
}
log.Info().Str("filePath", filePath).Int("schemaDefinitionCount", len(defs)).Msg("Loading schema definitions")
nsDefs = append(nsDefs, defs...)
// Load the namespace configs.
log.Info().Str("filePath", filePath).Int("namespaceCount", len(parsed.NamespaceConfigs)).Msg("Loading namespaces")
for index, namespaceConfig := range parsed.NamespaceConfigs {
nsDef := core.NamespaceDefinition{}
nerr := prototext.Unmarshal([]byte(namespaceConfig), &nsDef)
if nerr != nil {
return nil, revision, fmt.Errorf("error when parsing namespace config #%v from file %s: %w", index, filePath, nerr)
}
nsDefs = append(nsDefs, &nsDef)
}
// Load the namespaces and type check.
nsm := namespace.NewNonCachingNamespaceManager()
for _, nsDef := range nsDefs {
ts, err := namespace.BuildNamespaceTypeSystemWithFallback(nsDef, nsm, nsDefs, revision)
if err != nil {
return nil, revision, err
}
ctx := dsctx.ContextWithDatastore(context.Background(), ds)
vts, terr := ts.Validate(ctx)
if terr != nil {
return nil, revision, terr
}
aerr := namespace.AnnotateNamespace(vts)
if aerr != nil {
return nil, revision, aerr
}
log.Info().Str("filePath", filePath).Str("namespaceName", nsDef.Name).Msg("Loading namespace")
rev, lnerr := ds.WriteNamespace(context.Background(), nsDef)
if lnerr != nil {
return nil, revision, fmt.Errorf("error when loading namespace %s: %w", nsDef.Name, lnerr)
}
revision = rev
}
// Load the validation tuples/relationships.
var updates []*v1.RelationshipUpdate
seenTuples := map[string]bool{}
for _, rel := range parsed.Relationships.Relationships {
updates = append(updates, &v1.RelationshipUpdate{
Operation: v1.RelationshipUpdate_OPERATION_CREATE,
Relationship: rel,
})
tpl := tuple.MustFromRelationship(rel)
tuples = append(tuples, tpl)
seenTuples[tuple.String(tpl)] = true
}
log.Info().Str("filePath", filePath).Int("tupleCount", len(updates)+len(parsed.ValidationTuples)).Msg("Loading test data")
for index, validationTuple := range parsed.ValidationTuples {
tpl := tuple.Parse(validationTuple)
if tpl == nil {
return nil, decimal.Zero, fmt.Errorf("error parsing validation tuple #%v: %s", index, validationTuple)
}
_, ok := seenTuples[tuple.String(tpl)]
if ok {
continue
}
seenTuples[tuple.String(tpl)] = true
tuples = append(tuples, tpl)
updates = append(updates, &v1.RelationshipUpdate{
Operation: v1.RelationshipUpdate_OPERATION_CREATE,
Relationship: tuple.MustToRelationship(tpl),
})
}
wrevision, terr := ds.WriteTuples(context.Background(), nil, updates)
if terr != nil {
return nil, decimal.Zero, fmt.Errorf("error when loading validation tuples from file %s: %w", filePath, terr)
}
revision = wrevision
}
return &PopulatedValidationFile{schema, nsDefs, tuples, files}, revision, nil
}