go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/impl/metadata/legacy.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package metadata 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strings" 22 "sync" 23 "time" 24 25 "google.golang.org/protobuf/proto" 26 "google.golang.org/protobuf/types/known/timestamppb" 27 28 "go.chromium.org/luci/auth/identity" 29 "go.chromium.org/luci/common/errors" 30 "go.chromium.org/luci/common/logging" 31 "go.chromium.org/luci/common/retry/transient" 32 "go.chromium.org/luci/common/sync/parallel" 33 "go.chromium.org/luci/gae/service/datastore" 34 35 api "go.chromium.org/luci/cipd/api/cipd/v1" 36 "go.chromium.org/luci/cipd/common" 37 ) 38 39 // rootMeta is metadata of the root prefix, it is inherited by all prefixes. 40 // 41 // TODO(vadimsh): Make this configurable. 42 var rootMeta = &api.PrefixMetadata{ 43 Acls: []*api.PrefixMetadata_ACL{ 44 // Administrators have implicit permissions to do everything everywhere. 45 { 46 Role: api.Role_OWNER, 47 Principals: []string{"group:administrators"}, 48 }, 49 }, 50 } 51 52 func init() { 53 CalculateFingerprint(rootMeta) 54 } 55 56 // legacyStorageImpl implements Storage on top of PackageACL entities inherited 57 // from Python version of CIPD backend. 58 // 59 // This implementation stores api.PrefixMetadata in a deconstructed form as a 60 // bunch of datastore entities to be compatible with Python version of the 61 // backend that has no idea about PrefixMetadata abstraction. 62 // 63 // The processes of constructing and deconstructing PrefixMetadata are not 64 // perfectly reversible: 65 // - Order of ACL entries in PrefixMetadata is not preserved. 66 // - Order of principals in ACLs is also not preserved. 67 // - Empty ACLs are removed from PrefixMetadata. 68 // 69 // These differences shouldn't have semantic importance for users through. 70 type legacyStorageImpl struct { 71 } 72 73 // GetMetadata is part of Storage interface. 74 // 75 // For each subprefix in 'prefix' (e.g. for "a/b/c" it would be "a", "a/b", ...) 76 // it fetches a bunch of packageACL entities (one per possible role), and merges 77 // them into single PrefixMetadata object, thus faking new metadata API on top 78 // of legacy entities. 79 // 80 // The result also always includes the hardcoded root metadata. 81 func (legacyStorageImpl) GetMetadata(ctx context.Context, prefix string) ([]*api.PrefixMetadata, error) { 82 md, _, err := getMetadataImpl(ctx, prefix) 83 return md, err 84 } 85 86 // getMetadataImpl implements GetMetadata. 87 // 88 // As a bonus it returns all packageACL entities it fetched. This is used by 89 // VisitMetadata to avoid unnecessary refetches. 90 func getMetadataImpl(ctx context.Context, prefix string) ([]*api.PrefixMetadata, []*packageACL, error) { 91 prefix, err := common.ValidatePackagePrefix(prefix) 92 if err != nil { 93 return nil, nil, errors.Annotate(err, "bad prefix given to GetMetadata").Err() 94 } 95 96 // Grab all subprefixes, i.e. ["a", "a/b", "a/b/c"] 97 var pfxs []string 98 for i, ch := range prefix { 99 if ch == '/' { 100 pfxs = append(pfxs, prefix[:i]) 101 } 102 } 103 pfxs = append(pfxs, prefix) 104 105 // Start with the root metadata. 106 out := make([]*api.PrefixMetadata, 0, len(pfxs)+1) 107 out = append(out, rootMetadata()) 108 109 // And finish with it if nothing else is requested. 110 if len(pfxs) == 0 { 111 return out, nil, nil 112 } 113 114 // Prepare the keys. 115 ents := make([]*packageACL, 0, len(pfxs)*len(legacyRoles)) 116 for _, p := range pfxs { 117 ents = prefixACLs(ctx, p, ents) 118 } 119 120 // Fetch everything. ErrNoSuchEntity errors are fine, everything else is not. 121 if err = datastore.Get(ctx, ents); isInternalDSError(err) { 122 return nil, nil, errors.Annotate(err, "datastore error when fetching PackageACL").Tag(transient.Tag).Err() 123 } 124 125 // Combine the result into a bunch of PrefixMetadata structs. 126 legLen := len(legacyRoles) 127 for i, pfx := range pfxs { 128 if md := mergeIntoPrefixMetadata(ctx, pfx, ents[i*legLen:(i+1)*legLen]); md != nil { 129 out = append(out, md) 130 } 131 } 132 return out, ents, nil 133 } 134 135 // VisitMetadata is part of Storage interface. 136 func (legacyStorageImpl) VisitMetadata(ctx context.Context, prefix string, cb Visitor) error { 137 prefix, err := common.ValidatePackagePrefix(prefix) 138 if err != nil { 139 return errors.Annotate(err, "bad prefix given to VisitMetadata").Err() 140 } 141 142 // Visit 'prefix' directly first, per VisitMetadata contract. There's a chance 143 // we won't need to recurse deeper at all and can skip all expensive fetches. 144 md, ents, err := getMetadataImpl(ctx, prefix) 145 if err != nil { 146 return err 147 } 148 switch cont, err := cb(prefix, md); { 149 case err != nil: 150 return err 151 case !cont: 152 return nil 153 } 154 155 // We'll have to recurse into metadata subtree after all. Unfortunately, 156 // with legacy entity structure there's no way to efficiently fetch only 157 // immediate children of 'prefix', so we fetch EVERYTHING in advance, building 158 // a metadata graph for prefix/... in memory. 159 gr := metadataGraph{} 160 gr.init(rootMetadata()) 161 162 // Seed this graph with already fetched entities. They'll be needed to derive 163 // metadata inherited from 'prefix' and its parents when visiting nodes. For 164 // example, the metadata graph when visiting prefix "a/b" may look like this: 165 // 166 // - "a/b/c" 167 // / 168 // ROOT - "a"- "a/b" -- "a/b/d" 169 // \ 170 // - "a/b/e" 171 // 172 // The traversal will be started form "a/b", but we still need the nodes 173 // leading to the root to get all inherited metadata. 174 gr.insert(ctx, ents) 175 176 // Fetch each per-role subtree separately, they have different key prefixes. 177 err = parallel.FanOutIn(func(tasks chan<- func() error) { 178 mu := sync.Mutex{} 179 for _, role := range legacyRoles { 180 role := role 181 tasks <- func() error { 182 listing, err := listACLsByPrefix(ctx, role, prefix) 183 if err == nil { 184 mu.Lock() 185 gr.insert(ctx, listing) 186 mu.Unlock() 187 } 188 return err 189 } 190 } 191 }) 192 if err != nil { 193 return errors.Annotate(err, "failed to fetch metadata").Tag(transient.Tag).Err() 194 } 195 196 // Make sure we have a path to 'prefix' before we freeze the graph. We need it 197 // to start the traversal below. It may be missing if there's no metadata 198 // attached to it and it has no children. This will naturally be handled by 199 // 'traverse'. 200 pfx := gr.node(prefix) 201 202 // Calculate all PrefixMetadata entries in the graph. 203 gr.freeze(ctx) 204 205 // Traverse the graph, but make sure to skip 'prefix' itself, we've already 206 // visited it at the very beginning. 207 return pfx.traverse(nil, func(n *metadataNode, md []*api.PrefixMetadata) (cont bool, err error) { 208 switch { 209 case n.prefix == prefix: 210 return true, nil 211 case n.md == nil: 212 return true, nil // an intermediary node with no metadata, look deeper 213 default: 214 return cb(n.prefix, md) 215 } 216 }) 217 } 218 219 // UpdateMetadata is part of Storage interface. 220 // 221 // It assembles prefix metadata from a bunch of packageACL entities, passes it 222 // to the callback for modification, then deconstructs it back into a bunch of 223 // packageACL entities, to be saved in the datastore. All done transactionally. 224 func (legacyStorageImpl) UpdateMetadata(ctx context.Context, prefix string, cb func(ctx context.Context, m *api.PrefixMetadata) error) (*api.PrefixMetadata, error) { 225 prefix, err := common.ValidatePackagePrefix(prefix) 226 if err != nil { 227 return nil, errors.Annotate(err, "bad prefix given to GetMetadata").Err() 228 } 229 if prefix == "" { 230 return nil, errors.Reason("the root metadata is not modifiable").Err() 231 } 232 233 var cbErr error // error from 'cb' 234 var updated *api.PrefixMetadata // updated metadata to return 235 236 err = datastore.RunInTransaction(ctx, func(ctx context.Context) error { 237 cbErr = nil // reset in case the transaction is being retried 238 updated = nil 239 240 // Fetch the existing metadata. 241 ents := prefixACLs(ctx, prefix, nil) 242 if err := datastore.Get(ctx, ents); isInternalDSError(err) { 243 return errors.Annotate(err, "datastore error when fetching PackageACL").Err() 244 } 245 246 // Convert it to PrefixMetadata object. This will be nil if there's no 247 // existing metadata, in which case we construct the default metadata 248 // with no fingerprint (to indicate it is new), see UpdateMetadata doc in 249 // Storage interface. 250 updated = mergeIntoPrefixMetadata(ctx, prefix, ents) 251 if updated == nil { 252 updated = &api.PrefixMetadata{Prefix: prefix} 253 } 254 255 // Let the callback update the metadata. Retain the old copy for diff later. 256 before := proto.Clone(updated).(*api.PrefixMetadata) 257 if cbErr = cb(ctx, updated); cbErr != nil { 258 return cbErr 259 } 260 261 // Don't let the callback mess with the prefix or the fingerprint. 262 updated.Prefix = before.Prefix 263 updated.Fingerprint = before.Fingerprint 264 if proto.Equal(before, updated) { 265 return nil // no changes whatsoever, don't touch anything 266 } 267 268 // Apply changes to the datastore. This updates 'ents' to match the metadata 269 // stored in 'updated'. We then rederive PrefixMetadata (including the new 270 // fingerprint) from them. We do it this way (instead of calculating the 271 // fingerprint using 'updated' directly), to be absolutely sure that the 272 // fingerprint returned by GetMetadata after this transaction lands matches 273 // the fingerprint we return from UpdateMetadata. In particular, the way 274 // we store ACLs in legacy entities doesn't preserve order of Acls entries 275 // in the proto, or order of principals inside Acls, so we need to 276 // "reformat" the updated metadata before calculating its fingerprint. 277 if err := applyACLDiff(ctx, ents, updated); err != nil { 278 return errors.Annotate(err, "failed to update PackageACL entities").Err() 279 } 280 updated = mergeIntoPrefixMetadata(ctx, prefix, ents) 281 return nil 282 }, nil) 283 284 switch { 285 case cbErr != nil: 286 // The callback itself failed, need to return the error as is, as promised. 287 return nil, cbErr 288 case err != nil: 289 // All other errors are from the datastore, consider them transient. 290 return nil, errors.Annotate(err, "transaction failed").Tag(transient.Tag).Err() 291 case updated == nil || updated.Fingerprint == "": 292 // This happens if there's no existing metadata and the callback didn't 293 // create it. Return nil to indicate that the metadata is still missing. 294 return nil, nil 295 } 296 return updated, nil 297 } 298 299 //////////////////////////////////////////////////////////////////////////////// 300 // Legacy entities with fields matching Python version of the CIPD backend. 301 302 // legacyRoles contain all roles that can show up as part of packageACL key. 303 // 304 // There's also COUNTER_WRITER legacy role that we skip: counters are 305 // deprecated. 306 var legacyRoles = []string{ 307 "OWNER", 308 "WRITER", 309 "READER", 310 } 311 312 // legacyRoleMap is mapping from legacy role names to non-legacy ones. 313 // 314 // Note: we can't use keys of this map instead of legacyRoles slice, since we 315 // always want to enumerate roles in order. 316 var legacyRoleMap = map[string]api.Role{ 317 "OWNER": api.Role_OWNER, 318 "WRITER": api.Role_WRITER, 319 "READER": api.Role_READER, 320 } 321 322 // packageACL contains ACLs for some package prefix. 323 type packageACL struct { 324 _kind string `gae:"$kind,PackageACL"` 325 _extra datastore.PropertyMap `gae:"-,extra"` 326 327 ID string `gae:"$id"` // "<ROLE>:<pkg/path>" 328 Parent *datastore.Key `gae:"$parent"` // see rootKey() 329 Rev int `gae:"rev,noindex"` // incremented on each change 330 331 Users []string `gae:"users,noindex"` 332 Groups []string `gae:"groups,noindex"` 333 ModifiedBy string `gae:"modified_by"` 334 ModifiedTS time.Time `gae:"modified_ts"` 335 } 336 337 // parseKey parses the entity key into its components, validating them. 338 // 339 // On success, role is one of legacyRoles and prefix is non-empty valid prefix. 340 func (e *packageACL) parseKey() (role, prefix string, err error) { 341 chunks := strings.Split(e.ID, ":") 342 if len(chunks) != 2 { 343 return "", "", fmt.Errorf("invalid key %q, not <role>:<prefix> pair", e.ID) 344 } 345 role = chunks[0] 346 if _, ok := legacyRoleMap[role]; !ok { 347 return "", "", fmt.Errorf("unrecognized role in the key %q", e.ID) 348 } 349 prefix, err = common.ValidatePackagePrefix(chunks[1]) 350 if err != nil || prefix == "" { // note: there's no ACLs for root in the datastore 351 return "", "", fmt.Errorf("invalid package prefix in the key %q", e.ID) 352 } 353 return 354 } 355 356 // isInternalDSError is true for datastore errors that aren't entirely 357 // ErrNoSuchEntity. 358 func isInternalDSError(err error) bool { 359 if err == nil { 360 return false 361 } 362 363 merr, _ := err.(errors.MultiError) 364 if merr == nil { 365 return true // an overall RPC error 366 } 367 368 for _, e := range merr { 369 if e != nil && e != datastore.ErrNoSuchEntity { 370 return true // some serious issues with this single entity 371 } 372 } 373 374 return false // all suberrors are either nil or ErrNoSuchEntity 375 } 376 377 // rootMetadata returns metadata of the root prefix (always a new copy). 378 // 379 // It is inherited by all prefixes. 380 func rootMetadata() *api.PrefixMetadata { 381 return proto.Clone(rootMeta).(*api.PrefixMetadata) 382 } 383 384 // rootKey returns a key of the root entity that stores ACL hierarchy. 385 func rootKey(ctx context.Context) *datastore.Key { 386 return datastore.NewKey(ctx, "PackageACLRoot", "acls", 0, nil) 387 } 388 389 // prefixACLs appends empty packageACL entities (with keys populated) to the 390 // given slice and returns the new slice. 391 // 392 // The added entities have keys '<role>:<prefix>' where <role> goes over all 393 // possible roles. Adds exactly len(legacyRoles) entities. 394 func prefixACLs(ctx context.Context, prefix string, in []*packageACL) []*packageACL { 395 root := rootKey(ctx) 396 for _, r := range legacyRoles { 397 in = append(in, &packageACL{ 398 ID: r + ":" + prefix, 399 Parent: root, 400 }) 401 } 402 return in 403 } 404 405 // mergeIntoPrefixMetadata takes exactly len(legacyRoles) entities and combines 406 // them into a single PrefixMetadata object if at least one of the entities is 407 // not empty. Returns nil of all entities are empty. 408 // 409 // The entities are expected to have IDs matching ones generated by 410 // prefixACLs(ctx, prefix). Panics otherwise. 411 // 412 // Logs and skips invalid principal names (should not be happening in reality). 413 func mergeIntoPrefixMetadata(ctx context.Context, prefix string, ents []*packageACL) *api.PrefixMetadata { 414 if len(ents) != len(legacyRoles) { 415 panic(fmt.Sprintf("expecting %d entities, got %d", len(legacyRoles), len(ents))) 416 } 417 418 modTime := time.Time{} // max(ent.ModifiedTS) 419 md := api.PrefixMetadata{Prefix: prefix} // to be returned if not empty 420 421 for i, r := range legacyRoles { 422 pkgACL := ents[i] 423 if pkgACL == nil { 424 continue // not present, no ACL for role 'r' 425 } 426 427 if expectedID := r + ":" + prefix; pkgACL.ID != expectedID { 428 panic(fmt.Sprintf("expecting key %q, got %q", expectedID, pkgACL.ID)) 429 } 430 431 if pkgACL.ModifiedTS.IsZero() { 432 continue // zero body, no such entity in the datastore, skip 433 } 434 435 // Detect the most recently modified packageACL to use its modification time 436 // as overall update time of PrefixMetadata object. 437 if modTime.IsZero() || modTime.Before(pkgACL.ModifiedTS) { 438 modTime = pkgACL.ModifiedTS 439 md.UpdateUser = pkgACL.ModifiedBy 440 md.UpdateTime = timestamppb.New(modTime) 441 } 442 443 // Collect a list of principals defined in packageACL, skipping unrecognized 444 // ones. 445 principals := make([]string, 0, len(pkgACL.Users)+len(pkgACL.Groups)) 446 for _, u := range pkgACL.Users { 447 if _, err := identity.MakeIdentity(u); err != nil { 448 logging.Errorf(ctx, "Bad identity %q in PackageACL %q - %s", u, pkgACL.ID, err) 449 } else { 450 principals = append(principals, u) 451 } 452 } 453 for _, g := range pkgACL.Groups { 454 principals = append(principals, "group:"+g) 455 } 456 457 if len(principals) != 0 { 458 md.Acls = append(md.Acls, &api.PrefixMetadata_ACL{ 459 Role: legacyRoleMap[r], 460 Principals: principals, 461 }) 462 } 463 } 464 465 // If modTime is zero, then none of packageACL entities exist. Return nil in 466 // this case to indicate there's no metadata at all. Note that it is possible 467 // that some packageACL entities exist, but have empty ACLs. We consider this 468 // as NOT empty metadata (we still have modification time and modifying user 469 // information). Thus we do not check len(md.Acls) == 0 here. Callers will 470 // see PrefixMetadata{...} with empty ACLs field, this should be expected. 471 if modTime.IsZero() { 472 return nil 473 } 474 475 // Calculate the fingerprint now that we have assembled everything. 476 CalculateFingerprint(&md) 477 return &md 478 } 479 480 // applyACLDiff extracts ACLs from 'md', compares them to 'ents', and Puts all 481 // updated entities into the datastore, updating 'ents'. 482 // 483 // In the end 'ents' together contain the new updated metadata. 484 // 485 // 'ents' are expected to have len(legacyRoles) entries, ordered by legacyRoles 486 // roles. Panics otherwise. 487 func applyACLDiff(ctx context.Context, ents []*packageACL, md *api.PrefixMetadata) error { 488 if len(ents) != len(legacyRoles) { 489 panic(fmt.Sprintf("expecting %d entities, got %d", len(legacyRoles), len(ents))) 490 } 491 492 // Convert md.ACLs to a map role -> principals, for easier access. 493 perRole := make(map[api.Role][]string, len(md.Acls)) 494 for _, acl := range md.Acls { 495 perRole[acl.Role] = acl.Principals 496 } 497 498 // Entities to put into the datastore. 499 toPut := []*packageACL{} 500 501 for i, r := range legacyRoles { 502 oldACL := ents[i] // an instance of *packageACL 503 if expectedID := r + ":" + md.Prefix; oldACL.ID != expectedID { 504 panic(fmt.Sprintf("expecting key %q, got %q", expectedID, oldACL.ID)) 505 } 506 507 // Grab Users and Group from the updated metadata (in 'md') to compare them 508 // to what's in the oldACL. 509 users := make([]string, 0, len(oldACL.Users)) 510 groups := make([]string, 0, len(oldACL.Groups)) 511 for _, pr := range perRole[legacyRoleMap[r]] { 512 if strings.HasPrefix(pr, "group:") { 513 groups = append(groups, strings.TrimPrefix(pr, "group:")) 514 } else { 515 users = append(users, pr) 516 } 517 } 518 if isEqualStrSlice(users, oldACL.Users) && isEqualStrSlice(groups, oldACL.Groups) { 519 continue // no changes, do not touch this entity 520 } 521 522 // This ACL was modified! Update it. 523 oldACL.Rev++ 524 oldACL.Users = users 525 oldACL.Groups = groups 526 oldACL.ModifiedBy = md.UpdateUser 527 oldACL.ModifiedTS = md.UpdateTime.AsTime() 528 toPut = append(toPut, oldACL) 529 } 530 531 return datastore.Put(ctx, toPut) 532 } 533 534 func isEqualStrSlice(a, b []string) bool { 535 if len(a) != len(b) { 536 return false 537 } 538 for i := range a { 539 if a[i] != b[i] { 540 return false 541 } 542 } 543 return true 544 } 545 546 // listACLsByPrefix returns packageACL entities with ACLs for the given legacy 547 // role under the given prefix. 548 // 549 // The prefix should be in a form produced by ValidatePackagePrefix, i.e. no 550 // trailing / and "" denotes the root. ACLs for the prefix itself are NOT 551 // listed. Only ACLs strictly underneath are. 552 // 553 // The return value is sorted by prefix. 554 func listACLsByPrefix(ctx context.Context, role, prefix string) (acls []*packageACL, err error) { 555 if prefix, err = common.ValidatePackagePrefix(prefix); err != nil { 556 return nil, err 557 } 558 559 keyPfx := role + ":" 560 if prefix != "" { 561 keyPfx += prefix + "/" 562 } 563 564 root := rootKey(ctx) 565 566 // Note: __key__ queries are already ordered by key. 567 q := datastore.NewQuery("PackageACL").Ancestor(root) 568 q = q.Gt("__key__", datastore.KeyForObj(ctx, &packageACL{ 569 ID: keyPfx + " ", 570 Parent: root, 571 })) 572 q = q.Lt("__key__", datastore.KeyForObj(ctx, &packageACL{ 573 ID: keyPfx + "~", 574 Parent: root, 575 })) 576 577 if err = datastore.GetAll(ctx, q, &acls); err != nil { 578 return nil, errors.Annotate(err, "failed to query the list of ACLs").Tag(transient.Tag).Err() 579 } 580 return 581 } 582 583 //////////////////////////////////////////////////////////////////////////////// 584 // Metadata graph used by VisitMetadata implementation. 585 586 // metadataNode is a single node in the metadata tree. 587 // 588 // It can be in non-frozen (== under construction) and frozen (== constructed) 589 // states. 590 type metadataNode struct { 591 prefix string // this node's full prefix, e.g. "a/b/c" 592 acls []*packageACL // exactly len(legacyRoles) items with node's ACLs, nil when frozen 593 594 parent *metadataNode 595 children map[string]*metadataNode // direct children of the node 596 597 // md is finalized metadata derived from 'acls' in 'freeze'. 598 // 599 // It may be nil for intermediary nodes that do not have metadata attached to 600 // them. 601 md *api.PrefixMetadata 602 } 603 604 // assertFrozen panics if the node is not frozen yet. 605 func (n *metadataNode) assertFrozen() { 606 if n.acls != nil { 607 panic("not frozen yet") 608 } 609 } 610 611 // assertNonFrozen panics if the node is already frozen. 612 func (n *metadataNode) assertNonFrozen() { 613 if n.acls == nil { 614 panic("frozen already") 615 } 616 } 617 618 // child returns a direct child node, creating it if necessary. 619 func (n *metadataNode) child(name string) *metadataNode { 620 if c, ok := n.children[name]; ok { 621 return c 622 } 623 n.assertNonFrozen() 624 if n.children == nil { 625 n.children = make(map[string]*metadataNode, 1) 626 } 627 c := &metadataNode{ 628 parent: n, 629 acls: make([]*packageACL, len(legacyRoles)), 630 } 631 if n.prefix == "" { 632 c.prefix = name 633 } else { 634 c.prefix = n.prefix + "/" + name 635 } 636 n.children[name] = c 637 return c 638 } 639 640 // attachACL attaches a packageACL to this node. 641 // 642 // All such attached ACLs are merged into single PrefixMetadata in 'freeze'. 643 func (n *metadataNode) attachACL(role string, e *packageACL) { 644 n.assertNonFrozen() 645 646 // 'acls' are ordered by legacyRoles. Insert 'e' into the corresponding slot. 647 // Such ordering is required by mergeIntoPrefixMetadata used by 'freeze'. 648 for i, r := range legacyRoles { 649 if r == role { 650 if n.acls[i] != nil { 651 panic(fmt.Sprintf("metadata for role %q at %q is already attached", role, n.prefix)) 652 } 653 n.acls[i] = e 654 return 655 } 656 } 657 658 // 'attachACL' is called only with roles validated by packageACL.parseKey(), 659 // so this is impossible. 660 panic(fmt.Sprintf("unexpected impossible role %q", role)) 661 } 662 663 // freeze marks the node and its subtree as fully constructed, calculating their 664 // PrefixMetadata from attached ACLs. 665 // 666 // The context is used only for logging. 667 func (n *metadataNode) freeze(ctx context.Context) { 668 n.assertNonFrozen() 669 670 // md may be already non-nil for the root, this is fine. 671 if n.md == nil { 672 n.md = mergeIntoPrefixMetadata(ctx, n.prefix, n.acls) 673 } 674 n.acls = nil // mark as frozen, release unnecessary memory 675 676 for _, child := range n.children { 677 child.freeze(ctx) 678 } 679 } 680 681 // metadata returns this node's and all inherited metadata. 682 // 683 // Root metadata first. The return value is never nil. If there's no metadata, 684 // returns non-nil empty slice. 685 func (n *metadataNode) metadata() (md []*api.PrefixMetadata) { 686 n.assertFrozen() 687 if n.parent != nil { 688 md = n.parent.metadata() 689 } else { 690 md = make([]*api.PrefixMetadata, 0, 32) // 32 is picked arbitrarily 691 } 692 if n.md != nil { 693 md = append(md, n.md) 694 } 695 return 696 } 697 698 // traverse does depth-first traversal of the node's subtree starting from self. 699 // 700 // 'md', if non-nil, is metadata from all previous parents (starting from the 701 // root). If it is nil, all inherited metadata will be calculated from scratch. 702 // 703 // Note that non-nil empty 'md' slice is a valid value (it means there's nothing 704 // to inherit), no recalculation will be done in this case. 705 // 706 // Children are visited in lexicographical order. 707 func (n *metadataNode) traverse(md []*api.PrefixMetadata, cb func(*metadataNode, []*api.PrefixMetadata) (bool, error)) error { 708 n.assertFrozen() 709 710 if md == nil { 711 md = n.metadata() // calculate from scratch 712 } else { 713 if n.md != nil { 714 md = append(md, n.md) // just extend what we have 715 } 716 } 717 718 switch cont, err := cb(n, md); { 719 case err != nil: 720 return err 721 case !cont: 722 return nil 723 } 724 725 keys := make([]string, 0, len(n.children)) 726 for k := range n.children { 727 keys = append(keys, k) 728 } 729 sort.Strings(keys) 730 731 for _, k := range keys { 732 if err := n.children[k].traverse(md, cb); err != nil { 733 return err 734 } 735 } 736 return nil 737 } 738 739 // metadataGraph is a in-memory metadata graph used by VisitMetadata. 740 type metadataGraph struct { 741 root *metadataNode // matches prefix "" 742 } 743 744 // init initializes the root node. 745 func (g *metadataGraph) init(root *api.PrefixMetadata) { 746 if root != nil && root.Prefix != "" { 747 panic("the root node metadata should have empty prefix") 748 } 749 g.root = &metadataNode{ 750 acls: make([]*packageACL, len(legacyRoles)), 751 md: root, 752 } 753 } 754 755 // node returns a node at the given path, constructing it if necessary. 756 // 757 // 'path' here has a form "a/b/c", relative to the absolute root (""). 758 func (g *metadataGraph) node(path string) *metadataNode { 759 cur := g.root 760 if path != "" { 761 for _, elem := range strings.Split(path, "/") { 762 cur = cur.child(elem) 763 } 764 } 765 return cur 766 } 767 768 // insert attaches ACL in the given entities to nodes in the graph. 769 // 770 // Silently ignores empty entities (based on ModifiedTS value). Logs and ignores 771 // broken ones. The context is used only for logging. 772 func (g *metadataGraph) insert(ctx context.Context, ents []*packageACL) { 773 for _, e := range ents { 774 if e.ModifiedTS.IsZero() { 775 continue // zero body, no such entity in the datastore, skip 776 } 777 role, pfx, err := e.parseKey() 778 if err != nil { 779 logging.Errorf(ctx, "Skipping bad PackageACL entity - %s", err) 780 continue 781 } 782 g.node(pfx).attachACL(role, e) 783 } 784 } 785 786 // freeze finalizes graph construction by calculating all PrefixMetadata items. 787 // 788 // The graph is not modifiable after this point, but it becomes traversable. 789 // The context is used only for logging. 790 func (g *metadataGraph) freeze(ctx context.Context) { 791 g.root.freeze(ctx) 792 }