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  }