luci: go.chromium.org/luci/cipd/client/cipd Index | Files | Directories

package cipd

import "go.chromium.org/luci/cipd/client/cipd"

Package cipd implements client side of Chrome Infra Package Deployer.

Binary package file format (in free form representation):

<binary package> := <zipped data>
<zipped data> := DeterministicZip(<all input files> + <manifest json>)
<manifest json> := File{
  name: ".cipdpkg/manifest.json",
  data: JSON({
    "FormatVersion": "1",
    "PackageName": <name of the package>
  }),
}
DeterministicZip = zip archive with deterministic ordering of files and stripped timestamps

Main package data (<zipped data> above) is deterministic, meaning its content depends only on inputs used to built it (byte to byte): contents and names of all files added to the package (plus 'executable' file mode bit) and a package name (and all other data in the manifest).

Binary package data MUST NOT depend on a timestamp, hostname of machine that built it, revision of the source code it was built from, etc. All that information will be distributed as a separate metadata packet associated with the package when it gets uploaded to the server.

TODO: expand more when there's server-side package data model (labels and stuff).

Index

Package Files

acl.go action_plan.go aliases.go client.go delete_on_close.go install_client.go instance_enum.go json_structs.go resolver.go storage.go

Constants

const (
    // NotParanoid indicates that EnsurePackages should trust its metadata
    // directory: if a package is marked as installed there, it should be
    // considered correctly installed in the site root too.
    NotParanoid = deployer.NotParanoid

    // CheckPresence indicates that CheckDeployed should verify all files
    // that are supposed to be installed into the site root are indeed present
    // there, and reinstall ones that are missing.
    //
    // Note that it will not check file's content or file mode. Only its presence.
    CheckPresence = deployer.CheckPresence
)
const (
    // WithoutManifest indicates the function should skip manifest.
    WithoutManifest = deployer.WithoutManifest
    // WithManifest indicates the function should handle manifest.
    WithManifest = deployer.WithManifest
)
const (
    // CASFinalizationTimeout is how long to wait for CAS service to finalize
    // the upload in RegisterInstance.
    CASFinalizationTimeout = 5 * time.Minute

    // SetRefTimeout is how long to wait for an instance to be processed when
    // setting a ref in SetRefWhenReady.
    SetRefTimeout = 3 * time.Minute

    // TagAttachTimeout is how long to wait for an instance to be processed when
    // attaching tags in AttachTagsWhenReady.
    TagAttachTimeout = 3 * time.Minute
)
const (
    EnvCacheDir            = "CIPD_CACHE_DIR"
    EnvHTTPUserAgentPrefix = "CIPD_HTTP_USER_AGENT_PREFIX"
)

Environment variable definitions

Variables

var (
    // ErrFinalizationTimeout is returned if CAS service can not finalize upload
    // fast enough.
    ErrFinalizationTimeout = errors.New("timeout while waiting for CAS service to finalize the upload", transient.Tag)

    // ErrBadUpload is returned when a package file is uploaded, but servers asks
    // us to upload it again.
    ErrBadUpload = errors.New("package file is uploaded, but servers asks us to upload it again", transient.Tag)

    // ErrProcessingTimeout is returned by SetRefWhenReady or AttachTagsWhenReady
    // if the instance processing on the backend takes longer than expected. Refs
    // and tags can be attached only to processed instances.
    ErrProcessingTimeout = errors.New("timeout while waiting for the instance to become ready", transient.Tag)

    // ErrDownloadError is returned by FetchInstance on download errors.
    ErrDownloadError = errors.New("failed to download the package file after multiple attempts", transient.Tag)

    // ErrUploadError is returned by RegisterInstance on upload errors.
    ErrUploadError = errors.New("failed to upload the package file after multiple attempts", transient.Tag)

    // ErrEnsurePackagesFailed is returned by EnsurePackages if something is not
    // right.
    ErrEnsurePackagesFailed = errors.New("failed to update packages, see the log")
)
var (
    // ClientPackage is a package with the CIPD client. Used during self-update.
    ClientPackage = "infra/tools/cipd/${platform}"
    // UserAgent is HTTP user agent string for CIPD client.
    UserAgent = "cipd 2.2.9"
)

func MaybeUpdateClient Uses

func MaybeUpdateClient(ctx context.Context, opts ClientOptions, targetVersion, clientExe string, digests *digests.ClientDigestsFile) (common.Pin, error)

MaybeUpdateClient will update the client binary at clientExe (given as a native path) to targetVersion if it's out of date (based on its hash).

This update is done from the "infra/tools/cipd/${platform}" package, see ClientPackage. The function will use the given ClientOptions to figure out how to establish a connection with the backend. Its Root and CacheDir values are ignored (values derived from clientExe are used instead).

If given 'digests' is not nil, will make sure the hash of the downloaded client binary is in 'digests'.

Note that this function make sense only in a context of a default CIPD CLI client. Other binaries that link to cipd package should not use it, they'll be "updated" to the CIPD client binary.

type ActionError Uses

type ActionError struct {
    Action string     `json:"action"`
    Pin    common.Pin `json:"pin"`
    Error  JSONError  `json:"error,omitempty"`
}

ActionError holds an error that happened when working on the pin.

type ActionMap Uses

type ActionMap map[string]*Actions

ActionMap is a map of subdir to the Actions which will occur within it.

func (ActionMap) Log Uses

func (am ActionMap) Log(ctx context.Context)

Log prints the pending action to the logger installed in ctx.

func (ActionMap) LoopOrdered Uses

func (am ActionMap) LoopOrdered(cb func(subdir string, actions *Actions))

LoopOrdered loops over the ActionMap in sorted order (by subdir).

type Actions Uses

type Actions struct {
    ToInstall common.PinSlice `json:"to_install,omitempty"` // pins to be installed
    ToUpdate  []UpdatedPin    `json:"to_update,omitempty"`  // pins to be replaced
    ToRemove  common.PinSlice `json:"to_remove,omitempty"`  // pins to be removed
    ToRepair  []BrokenPin     `json:"to_repair,omitempty"`  // pins to be repaired

    Errors []ActionError `json:"errors,omitempty"` // all individual errors
}

Actions lists what the cipd.Client should do or did to ensure the state of some single subdirectory under the installation root.

Is it part of a per-directory ActionMap returned by EnsurePackages.

func (*Actions) Empty Uses

func (a *Actions) Empty() bool

Empty is true if there are no actions specified.

type BrokenPin Uses

type BrokenPin struct {
    Pin        common.Pin `json:"pin"`
    RepairPlan RepairPlan `json:"repair_plan"`
}

BrokenPin specifies a pin that should be repaired and how.

type Client Uses

type Client interface {
    // BeginBatch makes the client enter into a "batch mode".
    //
    // In this mode various cleanup and cache updates, usually performed right
    // away, are deferred until 'EndBatch' call.
    //
    // This is an optimization. Use it if you plan to call a bunch of Client
    // methods in a short amount of time (parallel or sequentially).
    //
    // Batches can be nested.
    BeginBatch(ctx context.Context)

    // EndBatch ends a batch started with BeginBatch.
    //
    // EndBatch does various delayed maintenance tasks (like cache updates, trash
    // cleanup and so on). This is best-effort operations, and thus this method
    // doesn't return an errors.
    //
    // See also BeginBatch doc for more details.
    EndBatch(ctx context.Context)

    // FetchACL returns a list of PackageACL objects (parent paths first).
    //
    // Together they define the access control list for the given package prefix.
    FetchACL(ctx context.Context, prefix string) ([]PackageACL, error)

    // ModifyACL applies a set of PackageACLChanges to a package prefix ACL.
    ModifyACL(ctx context.Context, prefix string, changes []PackageACLChange) error

    // FetchRoles returns all roles the caller has in the given package prefix.
    //
    // Understands roles inheritance, e.g. if the caller is OWNER, the return
    // value will list all roles implied by being an OWNER (e.g. READER, WRITER,
    // ...).
    FetchRoles(ctx context.Context, prefix string) ([]string, error)

    // ResolveVersion converts an instance ID, a tag or a ref into a concrete Pin.
    ResolveVersion(ctx context.Context, packageName, version string) (common.Pin, error)

    // RegisterInstance makes the package instance available for clients.
    //
    // It uploads the instance to the storage, waits until the storage verifies
    // its hash, and then registers the package in the repository, making it
    // discoverable.
    //
    // 'timeout' specifies for how long to wait until the instance hash is
    // verified by the storage backend. If 0, default CASFinalizationTimeout will
    // be used.
    RegisterInstance(ctx context.Context, instance pkg.Instance, timeout time.Duration) error

    // DescribeInstance returns information about a package instance.
    //
    // May also be used as a simple instance presence check, if opts is nil. If
    // the request succeeds, then the instance exists.
    DescribeInstance(ctx context.Context, pin common.Pin, opts *DescribeInstanceOpts) (*InstanceDescription, error)

    // DescribeClient returns information about a CIPD client binary matching the
    // given client package pin.
    DescribeClient(ctx context.Context, pin common.Pin) (*ClientDescription, error)

    // SetRefWhenReady moves a ref to point to a package instance.
    SetRefWhenReady(ctx context.Context, ref string, pin common.Pin) error

    // AttachTagsWhenReady attaches tags to an instance.
    AttachTagsWhenReady(ctx context.Context, pin common.Pin, tags []string) error

    // FetchPackageRefs returns information about all refs defined for a package.
    //
    // The returned list is sorted by modification timestamp (newest first).
    FetchPackageRefs(ctx context.Context, packageName string) ([]RefInfo, error)

    // FetchInstance downloads a package instance file from the repository.
    //
    // It verifies that the package hash matches pin.InstanceID.
    //
    // It returns an InstanceFile pointing to the raw package data. The caller
    // must close it when done.
    FetchInstance(ctx context.Context, pin common.Pin) (pkg.Source, error)

    // FetchInstanceTo downloads a package instance file into the given writer.
    //
    // This is roughly the same as getting a reader with 'FetchInstance' and
    // copying its data into the writer, except this call skips unnecessary temp
    // files if the client is not using cache.
    //
    // It verifies that the package hash matches pin.InstanceID, but does it while
    // writing to 'output', so expect to discard all data there if FetchInstanceTo
    // returns an error.
    FetchInstanceTo(ctx context.Context, pin common.Pin, output io.WriteSeeker) error

    // FetchAndDeployInstance fetches the package instance and deploys it.
    //
    // Deploys to the given subdir under the site root (see ClientOptions.Root).
    // It doesn't check whether the instance is already deployed.
    FetchAndDeployInstance(ctx context.Context, subdir string, pin common.Pin) error

    // ListPackages returns a list packages and prefixes under the given prefix.
    ListPackages(ctx context.Context, prefix string, recursive, includeHidden bool) ([]string, error)

    // SearchInstances finds instances of some package with all given tags.
    //
    // Returns their concrete Pins. If the package doesn't exist at all, returns
    // empty slice and nil error.
    SearchInstances(ctx context.Context, packageName string, tags []string) (common.PinSlice, error)

    // ListInstances enumerates instances of a package, most recent first.
    //
    // Returns an object that can be used to fetch the listing, page by page.
    ListInstances(ctx context.Context, packageName string) (InstanceEnumerator, error)

    // EnsurePackages installs, removes and updates packages in the site root.
    //
    // Given a description of what packages (and versions) should be installed it
    // will do all necessary actions to bring the state of the site root to the
    // desired one.
    //
    // Depending on the paranoia mode, will optionally verify that all installed
    // packages are installed correctly and will attempt to fix ones that are not.
    // See the enum for more info.
    //
    // If dryRun is true, will just check for changes and return them in Actions
    // struct, but won't actually perform them.
    //
    // If the update was only partially applied, returns both Actions and error.
    EnsurePackages(ctx context.Context, pkgs common.PinSliceBySubdir, paranoia ParanoidMode, dryRun bool) (ActionMap, error)
}

Client provides high-level CIPD client interface. Thread safe.

func NewClient Uses

func NewClient(opts ClientOptions) (Client, error)

NewClient initializes CIPD client object.

type ClientDescription Uses

type ClientDescription struct {
    InstanceInfo

    // Size of the client binary file in bytes.
    Size int64 `json:"size"`

    // SignedUrl is URL of the client binary.
    SignedUrl string `json:"signed_url"`

    // Digest is the client binary digest using the best hash algo understood by
    // the current process.
    //
    // May potentially be nil if the current process doesn't understand any of
    // the algos, but this is an extreme situation and it is OK to panic in this
    // case. At very least all client binaries have SHA1 digests, and it should
    // be understood by all clients.
    Digest *api.ObjectRef `json:"digest"`

    // AlternativeDigests is a list of digest calculated using hash algos other
    // than the one used by Digest.
    //
    // This may include both old hash algos (obsoleted by the algo in Digest), as
    // well as a new ones, not supported by the current process.
    //
    // Not all client versions have all digests calculated. Older versions have
    // only SHA1 digest, which means for them Digest will be SHA1 and
    // AlternativeDigests list will be empty.
    AlternativeDigests []*api.ObjectRef `json:"alternative_digests"`
}

ClientDescription contains extended information about a CIPD client binary at some version for some platform, as returned by DescribeClient.

type ClientOptions Uses

type ClientOptions struct {
    // ServiceURL is root URL of the backend service.
    //
    // Default is ServiceURL const.
    ServiceURL string

    // Root is a site root directory.
    //
    // It is a directory where packages will be installed to. It also hosts
    // .cipd/* directory that tracks internal state of installed packages and
    // keeps various cache files. 'Root' can be an empty string if the client is
    // not going to be used to deploy or remove local packages.
    Root string

    // CacheDir is a directory for shared cache.
    //
    // If empty, instances are not cached and tags are cached inside the site
    // root. If both Root and CacheDir are empty, tag cache is disabled.
    CacheDir string

    // Versions is optional database of (pkg, version) => instance ID resolutions.
    //
    // If set, it will be used for all version resolutions done by the client.
    // The client won't be consulting (or updating) the tag cache and won't make
    // 'ResolveVersion' backend RPCs.
    //
    // This is primarily used to implement $ResolvedVersions ensure file feature.
    Versions ensure.VersionsFile

    // AnonymousClient is http.Client that doesn't attach authentication headers.
    //
    // Will be used when talking to the Google Storage. We use signed URLs that do
    // not require additional authentication.
    //
    // Default is http.DefaultClient.
    AnonymousClient *http.Client

    // AuthenticatedClient is http.Client that attaches authentication headers.
    //
    // Will be used when talking to the backend.
    //
    // Default is same as AnonymousClient (it will probably not work for most
    // packages, since the backend won't authorize an anonymous access).
    AuthenticatedClient *http.Client

    // UserAgent is put into User-Agent HTTP header with each request.
    //
    // Default is UserAgent const.
    UserAgent string
    // contains filtered or unexported fields
}

ClientOptions is passed to NewClient factory function.

func (*ClientOptions) LoadFromEnv Uses

func (opts *ClientOptions) LoadFromEnv(getEnv func(string) string) error

LoadFromEnv loads supplied default values from an environment into opts.

The supplied getEnv function is used to access named enviornment variables, and should return an empty string if the enviornment variable is not defined.

type DescribeInstanceOpts Uses

type DescribeInstanceOpts struct {
    DescribeRefs bool // if true, will fetch all refs pointing to the instance
    DescribeTags bool // if true, will fetch all tags attached to the instance
}

DescribeInstanceOpts is passed to DescribeInstance.

type InstanceDescription Uses

type InstanceDescription struct {
    InstanceInfo

    // Refs is a list of refs pointing to the instance, sorted by modification
    // timestamp (newest first)
    //
    // Present only if DescribeRefs in DescribeInstanceOpts is true.
    Refs []RefInfo `json:"refs,omitempty"`

    // Tags is a list of tags attached to the instance, sorted by tag key and
    // creation timestamp (newest first).
    //
    // Present only if DescribeTags in DescribeInstanceOpts is true.
    Tags []TagInfo `json:"tags,omitempty"`
}

InstanceDescription contains extended information about an instance as returned by DescribeInstance.

type InstanceEnumerator Uses

type InstanceEnumerator interface {
    // Next returns up to 'limit' instances or 0 if there's no more.
    Next(ctx context.Context, limit int) ([]InstanceInfo, error)
}

InstanceEnumerator emits a list of instances, fetching them in batches.

Returned by ListInstances call.

type InstanceInfo Uses

type InstanceInfo struct {
    // Pin identifies package instance.
    Pin common.Pin `json:"pin"`
    // RegisteredBy is identity of whoever uploaded this instance.
    RegisteredBy string `json:"registered_by"`
    // RegisteredTs is when the instance was registered.
    RegisteredTs UnixTime `json:"registered_ts"`
}

InstanceInfo is information about single package instance.

type JSONError Uses

type JSONError struct {
    // contains filtered or unexported fields
}

JSONError is wrapper around Error that serializes it as string.

func (JSONError) MarshalJSON Uses

func (e JSONError) MarshalJSON() ([]byte, error)

MarshalJSON is used by JSON encoder.

type ManifestMode Uses

type ManifestMode = deployer.ManifestMode

ManifestMode is used to indicate presence of absence of manifest when calling various functions.

Just to improve code readability, since Func(..., WithManifest) is less cryptic than Func(..., true).

type PackageACL Uses

type PackageACL struct {
    // PackagePath is a package subpath this ACL is defined for.
    PackagePath string `json:"package_path"`
    // Role is a role that listed users have, e.g. 'READER', 'WRITER', ...
    Role string `json:"role"`
    // Principals list users and groups granted the role.
    Principals []string `json:"principals"`
    // ModifiedBy specifies who modified the list the last time.
    ModifiedBy string `json:"modified_by"`
    // ModifiedTs is a timestamp when the list was modified the last time.
    ModifiedTs UnixTime `json:"modified_ts"`
}

PackageACL is per package path per role access control list that is a part of larger overall ACL: ACL for package "a/b/c" is a union of PackageACLs for "a" "a/b" and "a/b/c".

type PackageACLChange Uses

type PackageACLChange struct {
    // Action defines what action to perform: GrantRole or RevokeRole.
    Action PackageACLChangeAction
    // Role to grant or revoke to a user or group, see Role enum repo.proto.
    Role string
    // Principal is a user or a group to grant or revoke a role for.
    Principal string
}

PackageACLChange is a mutation to some package ACL.

type PackageACLChangeAction Uses

type PackageACLChangeAction string

PackageACLChangeAction defines a flavor of PackageACLChange.

Used by ModifyACL.

const (
    // GrantRole is used in PackageACLChange to request a role to be granted.
    GrantRole PackageACLChangeAction = "GRANT"
    // RevokeRole is used in PackageACLChange to request a role to be revoked.
    RevokeRole PackageACLChangeAction = "REVOKE"
)

type ParanoidMode Uses

type ParanoidMode = deployer.ParanoidMode

ParanoidMode specifies how paranoid EnsurePackages should be.

type RefInfo Uses

type RefInfo struct {
    // Ref is the ref name.
    Ref string `json:"ref"`
    // InstanceID is ID of a package instance the ref points to.
    InstanceID string `json:"instance_id"`
    // ModifiedBy is identity of whoever modified this ref last time.
    ModifiedBy string `json:"modified_by"`
    // ModifiedTs is when the ref was modified last time.
    ModifiedTs UnixTime `json:"modified_ts"`
}

RefInfo is returned by DescribeInstance and FetchPackageRefs.

type RepairPlan Uses

type RepairPlan struct {
    // NeedsReinstall is true if the package is broken to the point it is simpler
    // to completely reinstall it.
    //
    // ReinstallReason contains explanation why the reinstall is needed.
    //
    // If NeedsReinstall is false, then the package may be repaired just by
    // extracting a bunch of files, specified in ToRedeploy list.
    NeedsReinstall bool `json:"needs_reinstall,omitempty"`

    // ReinstallReason is a human-readable reason of why the package should be
    // completely reinstalled rather than selectively repaired.
    ReinstallReason string `json:"reinstall_reason,omitempty"`

    // ToRedeploy is a list of slash-separated file names (as they are specified
    // inside the package file) that needs to be reextracted from the original
    // package and relinked into the site root in order to repair the deployment.
    //
    // If this list is not empty, it means we'll need an original package file
    // to repair the deployment.
    //
    // Set only if NeedsReinstall is false.
    ToRedeploy []string `json:"to_redeploy,omitempty"`

    // ToRelink is a list of slash-separated file names (as they are specified
    // inside the package file) that needs to be relinked into the site root in
    // order to repair the deployment.
    //
    // They are already present in the .cipd/* guts, so there's no need to fetch
    // the original package file to get them.
    //
    // Set only if NeedsReinstall is false.
    ToRelink []string `json:"to_relink,omitempty"`
}

RepairPlan describes what should be redeployed to fix a broken pin.

func (*RepairPlan) NumBrokenFiles Uses

func (p *RepairPlan) NumBrokenFiles() int

NumBrokenFiles returns number of files that will be repaired.

type Resolver Uses

type Resolver struct {
    // Client is the CIPD client to use for resolving versions.
    Client Client

    // VerifyPresence specifies whether the resolver should check the resolved
    // versions actually exist on the backend.
    VerifyPresence bool

    // Visitor is called for each package version that the resolver has
    // successfully processed.
    //
    // It receives the original (pkg, version) tuple and an instance ID it
    // resolves to.
    //
    // Called concurrently from multiple goroutines in undefined order. Same
    // (pkg, version) tuple may be visited multiple times. May be called for noop
    // version resolutions (when the version is already given as an instance ID).
    Visitor func(pkg, ver, iid string)
    // contains filtered or unexported fields
}

Resolver resolves versions of packages in an ensure file into concrete instance IDs.

For versions that are already defined as instance IDs, it verifies they actually exist.

The instance of Resolver is stateful. It caches results of resolutions and verifications, so that subsequent attempts to resolve/verify same pins are fast.

Resolver can be safely used concurrently.

func (*Resolver) Resolve Uses

func (r *Resolver) Resolve(ctx context.Context, file *ensure.File, expander template.Expander) (*ensure.ResolvedFile, error)

Resolve resolves versions of all packages in the ensure file using the given expander to expand templates.

Succeeds only if all packages have been successfully resolved and verified.

Names of packages that failed the resolution are returned as part of the multi-error.

func (*Resolver) ResolveAllPlatforms Uses

func (r *Resolver) ResolveAllPlatforms(ctx context.Context, file *ensure.File) (map[template.Platform]*ensure.ResolvedFile, error)

ResolveAllPlatforms resolves the ensure file for all platform it is verified for (see file.VerifyPlatforms list).

Doesn't stop on a first error. Collects them all into a single multi-error.

func (*Resolver) ResolvePackage Uses

func (r *Resolver) ResolvePackage(ctx context.Context, pkg, ver string) (pin common.Pin, err error)

ResolvePackage resolves the package's version into a concrete instance ID and (if Resolver.VerifyPresence is true) verifies it exists.

type TagInfo Uses

type TagInfo struct {
    // Tag is actual tag name ("key:value" pair).
    Tag string `json:"tag"`
    // RegisteredBy is identity of whoever attached this tag.
    RegisteredBy string `json:"registered_by"`
    // RegisteredTs is when the tag was registered.
    RegisteredTs UnixTime `json:"registered_ts"`
}

TagInfo is returned by DescribeInstance.

type UnixTime Uses

type UnixTime time.Time

UnixTime is time.Time that serializes to integer unix timestamp in JSON (represented as a number of seconds since January 1, 1970 UTC).

func (UnixTime) Before Uses

func (t UnixTime) Before(t2 UnixTime) bool

Before is used to compare UnixTime objects.

func (UnixTime) IsZero Uses

func (t UnixTime) IsZero() bool

IsZero reports whether t represents the zero time instant.

func (UnixTime) MarshalJSON Uses

func (t UnixTime) MarshalJSON() ([]byte, error)

MarshalJSON is used by JSON encoder.

func (UnixTime) String Uses

func (t UnixTime) String() string

String is needed to be able to print UnixTime.

type UpdatedPin Uses

type UpdatedPin struct {
    From common.Pin `json:"from"`
    To   common.Pin `json:"to"`
}

UpdatedPin specifies a pair of pins: old and new version of a package.

type UploadSession Uses

type UploadSession struct {
    // ID identifies upload session in the backend.
    ID  string
    // URL is where to upload the data to.
    URL string
}

UploadSession describes open CAS upload session.

Directories

PathSynopsis
builderPackage builder holds functionality for building CIPD packages.
deployerPackage deployer holds functionality for deploying CIPD packages.
digestsPackage digests holds types used by selfupdate mechanism to pin client hashes.
ensurePackage ensure contains methods and types for interacting with the 'ensure file format'.
fsPackage fs is file-system related utilities used internally by CIPD.
internal
internal/messages
local
pkgPackage pkg contains interfaces and struct related to CIPD package files.
platformPackage platform contains definition of what ${os} and ${arch} mean for the current platform.
templatePackage template implements handling of package name templates.

Package cipd imports 39 packages (graph) and is imported by 8 packages. Updated 2018-10-19. Refresh now. Tools for package owners.