blob: 4e1d0d1f83c114a231466e5a85143139532564c3 [file] [log] [blame]
// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metadata
import (
"context"
api "go.chromium.org/luci/cipd/api/cipd/v1"
)
// Visitor is a callback passed to VisitMetadata.
//
// It decides whether to continue exploring this metadata subtree or not.
type Visitor func(prefix string, md []*api.PrefixMetadata) (cont bool, err error)
// Storage knows how to store, fetch and update prefix metadata, as well as
// how to calculate its fingerprint.
//
// The metadata is organized in a forest-like structure, where each node is
// associated with some package prefix (e.g. has a name "a/b/c"). The single
// root ("") may or may not be present, depending on the implementation.
//
// It doesn't try to understand what metadata means, just fingerprints, stores
// and enumerates it.
//
// This functionality is organized into an interface to simplify mocking. Use
// GetStorage to grab a real implementation.
type Storage interface {
// GetMetadata fetches metadata associated with the given prefix and all
// parent prefixes.
//
// The prefix may be an empty string, in which case the root metadata will be
// returned, if it is defined.
//
// Does not check permissions.
//
// The return value is sorted by the prefix length. Prefixes without metadata
// are skipped. For example, when requesting metadata for prefix "a/b/c/d" the
// return value may contain entries for "", "a", "a/b", "a/b/c/d" (in that
// order, where "" indicates the root and "a/b/c" is skipped in this example
// as not having any metadata attached).
//
// Note that the prefix of the last entry doesn't necessary match 'prefix'.
// This happens if metadata for that prefix doesn't exist. Similarly, the
// returns value may be completely empty slice in case there's no metadata
// for the requested prefix and all its parent prefixes.
//
// Returns a fatal error if the prefix is malformed, all other errors are
// transient.
GetMetadata(c context.Context, prefix string) ([]*api.PrefixMetadata, error)
// VisitMetadata enumerates the metadata in depth-first order.
//
// Can be used to fetch all metadata items at and under the given prefix.
//
// The callback is called for each visited node (always starting from 'prefix'
// itself, even if it has no metadata directly attached to it), receiving same
// metadata list as if GetMetadata was used to fetch it. The callback can
// decide whether to proceed with the enumeration of the corresponding subtree
// or skip it (by returning either true or false).
//
// Aborts the traversal on a first error from the callback.
//
// Returns either a transient error if fetching failed, or whatever error the
// callback returned.
VisitMetadata(c context.Context, prefix string, cb Visitor) error
// UpdateMetadata transactionally (with XG transaction) updates or creates
// metadata of some prefix and returns it.
//
// The prefix may be an empty string, in which case the root metadata will
// be updated, if allowed.
//
// Does not check permissions. Does not check the format of the updated
// metadata.
//
// It fetches the metadata object and calls the callback to modify it. The
// callback may be called multiple times when retrying the transaction. If the
// callback mutates the metadata and doesn't return an error, the metadata's
// fingerprint is updated, the metadata is saved to the storage and returned
// to the caller.
//
// If the metadata object doesn't exist yet, the callback will be called with
// an empty object that has only 'Prefix' field populated. The callback then
// can populate the rest of the fields. If it doesn't touch any fields
// (but still succeeds), UpdateMetadata will return nil PrefixMetadata, to
// indicate the metadata is still absent.
//
// If the callback returns an error, it will be returned as is. If the
// transaction itself fails, returns a transient error.
UpdateMetadata(c context.Context, prefix string, cb func(m *api.PrefixMetadata) error) (*api.PrefixMetadata, error)
}
// GetStorage returns production implementation of the metadata storage.
func GetStorage() Storage {
return &legacyStorageImpl{}
}