Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial Plugin specification #132

Merged
merged 51 commits into from
Oct 25, 2024
Merged

Add initial Plugin specification #132

merged 51 commits into from
Oct 25, 2024

Conversation

mfridman
Copy link
Member

@mfridman mfridman commented Sep 25, 2024

This PR adds a new Plugin entity type. It is modelled like the Module entity.

A plugin and a module both have immutable commits and a label history. Which means they can be similarly referenced within configuration files by a label or an explicit commit.

Note, given the similarities between commits and labels, there's an alternative proposal by @doriable in #132 (comment) to avoid duplicating common definitions.

Copy link

github-actions bot commented Sep 25, 2024

The latest Buf updates on your PR. Results from workflow Buf CI / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedOct 25, 2024, 1:17 PM

@doriable
Copy link
Member

At the top-level, it seems like we are making carbon copies of definitions for resources, commits, and labels here, which makes sense from the perspective that these are shared concepts. But because of that, I also think rather than making copies, we should actually be defining our APIs to reflect the fact that they are shared. My thoughts are something that looks like:

(main %=)🐱~/src/buf/registry-proto @ $ tree .
.
├── buf
│   └── registry
│       ├── resource
│       │   ├── module
│       │   │   └── v1
│       │   │       ├── file.proto
│       │   │       ├── graph.proto
│       │   │       ├── graph_service.proto
│       │   │       ├── module.proto
│       │   │       └── module_service.proto
│       │   ├── plugin
│       │   │   └── v1
│       │   │       ├── code_generation.proto
│       │   │       ├── collection.proto
│       │   │       ├── plugin.proto
│       │   │       └── plugin_service.proto
│       │   └── v1
│       │       ├── commit.proto
│       │       ├── commit_service.proto
│       │       ├── digest.proto
│       │       ├── download_service.proto
│       │       ├── label.proto
│       │       ├── label_service.proto
│       │       ├── resource.proto
│       │       ├── resource_service.proto
│       │       └── upload_service.proto
│       ├── module (same as current)
│       ├── owner (same as current)
│       └── priv (same as current)
├── buf.lock
└── buf.yaml

Or alternatively, a slightly less nested structure:

(main %=)🐱~/src/buf/registry-proto @ $ tree .
.
├── buf
│   └── registry
│       ├── module
│       │   ├── v1 (same as current)
│       │   ├── v1beta1 (same as current)
│       │   └── v2
│       │       ├── file.proto
│       │       ├── graph.proto
│       │       ├── graph_service.proto
│       │       ├── module.proto
│       │       └── module_service.proto
│       ├── owner (same as current)
│       ├── plugin
│       │   └── v1
│       │       ├── code_generation.proto
│       │       ├── collection.proto
│       │       ├── plugin.proto
│       │       └── plugin_service.proto
│       ├── priv (same as current)
│       └── resource
│           └── v1
│               ├── commit.proto
│               ├── commit_service.proto
│               ├── digest.proto
│               ├── download_service.proto
│               ├── label.proto
│               ├── label_service.proto
│               ├── resource.proto
│               ├── resource_service.proto
│               └── upload_service.proto
├── buf.lock
└── buf.yaml

In either case though, I think the API should reflect what the shared entities are. This proposal would also come with the following changes:

  • This would add P1 as a new digest type to the overall digests, and then I think I would want to use protovalidate rules to enforce B* digests for module and P1 digest for plugins.
  • Download/upload contents would now be one-ofs of module content, check plugin content, code generation content
  • Resource would now be one-of the following: Module, Plugin, Label, or Commit

I am open to different names other than "resource", this just felt consistent with what we have right now. But basically, I think the premise of this is that if there were any foundational changes to either labels and/or commits, right now it's implicit that you need to update both modules and plugins, whereas this would make it explicit.

repeated ScopedLabelRef scoped_label_refs = 3;
}
// The Contents of all references.
repeated Content contents = 1 [(buf.validate.field).repeated.min_items = 1];
Copy link
Member Author

@mfridman mfridman Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugins might get quite large, so we should consider the batched nature of this API.

Unlike modules, users will likely be pushing a single plugin. Furthermore, I don't think we have plans for the buf plugin push command to support multiple plugin uploads.

Lastly, there's a cost in keeping these in memory. That was one of the motivations for compressing the content payload, because we can keep a smaller footprint. E.g., the ml plugin compressed went from 16MB -> 3MB. We also need to invoke and validate each plugin.

Options:

  1. Keep repeated, but add a validation rule for exactly one
  2. Keep repeated and min_items, but validate on the server
  3. Remove repeated, allow 1 upload at a time

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every other API in registry-proto operates on batches. In the absence of a concrete performance problem, there is no current reason to change the API to be inconsistent with other APIs. Therefore, no change here is required at this time.

// be overwritten.
bool managed = 11;
// The collections the Plugin belongs to.
repeated Collection collections = 12;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collection, as it is written, is an entity. It has an ID, and other fields that look like entities. Throughout this API, when one entity references another, it does so by ID, and each entity has a service that allows access to it. We don't embed entity messages in other entity messages.

(buf.validate.field).string.max_len = 255,
(buf.validate.field).ignore = IGNORE_IF_UNPOPULATED
];
// Managed indicates whether a Plugin is managed by the BSR.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field doesn't make sense to me - what do you mean "they will be overwritten?" What do you mean "automatically updated"? These arent entity properties, this is a function of how users use the API.

This field feels incorrect and like it shouldn't be here.

Copy link
Member Author

@mfridman mfridman Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this from the initial proposal. Will revisit this once we need this functionality.

For future, the intention is to have a way to differentiate between plugins that are custom (user-uploaded) or ones that are managed by the registry via a background job.

Need to think of a better name, managed is confusing.

(buf.validate.field).ignore = IGNORE_IF_UNPOPULATED
];
// The Plugin type.
PluginType type = 6 [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean to create a plugin of type PLUGIN_TYPE_PROTOC? More generally, what does it mean to create a plugin without any commits? How is (or how is this not) congruent with Modules?

// The collections to list Plugins for.
//
// If empty, all Plugins are listed regardless of collection.
repeated string collection_names = 4;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you referencing collection names when Collection is an entity?

enum PluginType {
PLUGIN_TYPE_UNSPECIFIED = 0;
// PLUGIN_TYPE_CHECK says that the Plugin is a check plugin.
PLUGIN_TYPE_CHECK = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a comment about PLUGIN_TYPE_PROTOC that was deleted from the current view, because PLUGIN_TYPE_PROTOC was removed without us concluding the discussion around whether or not it should be included. I was confused when I couldn't find the discussion.

The answer to whether PLUGIN_TYPE_PROTOC should be included depends on a few areas:

  • Can you map version/revision to label/commit?
  • What does it mean to request a download of a protoc plugin? What error is returned otherwise?
  • How do you explain in the docs that you cannot upload a plugin of type protoc?

- Removed managed from Plugin
- Add collection_ids to Plugin
- Pull in buf.build/bufbuild/bufplugin and PluginInfo
- Add source_control_url to Commit
@mfridman
Copy link
Member Author

To recap:

  • Make collection a well-defined entity with a read-only service to GetCollections and ListCollections. This is a many-many relationship, so we we'll have collections_ids on the Plugin.
  • Remove the Plugin managed field. We don't need it initially. We will revisit it once we have Buf-managed check plugins.
  • description and source_url belong to Plugin / Module entity, updateable via the UI or API.
    • documentation and source_control_url belong to commits, immutable.
    • bufbuild/bufplugin updated accordingly
  • Okay to depend on bufplugin protos, pull them in as a dependency. Add PluginInfo to Commit.
  • Punt on protoc plugins. Focus on check plugins. We don't need to solve this to ship check plugins.
  • Potentially some renaming of the bufplugin to just plugin, unlikely to change to naming to "extensions".

@@ -2,6 +2,7 @@ version: v2
name: buf.build/bufbuild/registry
deps:
- buf.build/bufbuild/protovalidate
- buf.build/bufbuild/bufplugin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extreme nit: I would sort these alphabetically, to match the lock file

}
}

message GetCollectionsRequest {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's up with these?

(buf.validate.field).ignore = IGNORE_IF_UNPOPULATED
];
// The plugin info of the Commit.
buf.plugin.info.v1.PluginInfo info = 8;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plugin_info

(buf.validate.field).string.max_len = 255,
(buf.validate.field).ignore = IGNORE_IF_UNPOPULATED
];
// The plugin info of the Commit.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a bit more. Something like "plugin information as stored directly within the plugin. This is part of the Bufplugin API, and can be returned from a plugin's GetPluginInfo implementation."

];
}
// The Contents of all references.
repeated Content contents = 1 [(buf.validate.field).repeated.min_items = 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our conversation: OK to reintroduce CompressionType and compression here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For brevity, do we also want to have compression type on the download service as well for content?

option idempotency_level = NO_SIDE_EFFECTS;
}
// Get the Collections for the given Plugins.
rpc GetCollectionsPlugins(GetCollectionsPluginsRequest) returns (GetCollectionsPluginsResponse) {
Copy link
Member Author

@mfridman mfridman Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bufdev not sure how much you hate this, but as @emcfarlane and I flushed this model out it seemed better to remove collection_ids from the Plugin entity and instead have a flattened list via this RPC.

Happy to bring back collection_ids on Plugin, but figured I'd add this for discussion. Say if you have PluginRefs and want to know which collection each of those belongs to you would:

  1. Call this endpoint with plugin refs
  2. Resolve the set of collection_id against GetCollections

@bufdev
Copy link
Member

bufdev commented Oct 24, 2024

I would probably keep in v1beta1 for a little bit unless you're supremely confident this is the final shape, but up to you

@mfridman
Copy link
Member Author

mfridman commented Oct 25, 2024

I changed it back to v1beta1 in dea5e2a.

I'd feel more confident marking it as v1 once we've flushed out code generation plugins (if we decide to do that).

@mfridman mfridman merged commit 81aff04 into main Oct 25, 2024
5 checks passed
@mfridman mfridman deleted the mf/plugins branch October 25, 2024 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants