diff --git a/CHANGELOG.md b/CHANGELOG.md index 545e830e20..8640fa7654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ file. Only WebAssembly check plugins are supported at this time. - Add `buf registry plugin commit {add-label,info,list,resolve}` to manage BSR plugin commits. - Add `buf registry plugin label {archive,info,list,unarchive}` to manage BSR plugin commits. +- Support remote check plugins in `buf lint` and `buf breaking` commands. ## [v1.47.2] - 2024-11-14 diff --git a/private/buf/bufctl/controller.go b/private/buf/bufctl/controller.go index d4543c502e..c562cc886a 100644 --- a/private/buf/bufctl/controller.go +++ b/private/buf/bufctl/controller.go @@ -133,8 +133,9 @@ type Controller interface { // GetCheckRunnerProvider gets a CheckRunnerProvider for the given input. // // The returned RunnerProvider will be able to run lint and breaking checks - // using the PluginConfigs from the input. The input provided will resolve - // the PluginKeys from the related buf.lock file. + // using the PluginConfigs declared in the input buf.yaml. The input + // provided will resolve to a Workspace, and the remote PluginConfigs + // Refs will be resolved to the PluginKeys from the buf.lock file. GetCheckRunnerProvider( ctx context.Context, input string, @@ -1201,32 +1202,44 @@ Declare %q in the deps key in your buf.yaml.`, // getPluginKeyProviderForRef create a new PluginKeyProvider for the Ref. // -// Remote plugins Refs are resolved to PluginKeys from the workspace buf.lock file. -// If the Ref is a MessageRef, we use the current directory buf.lock file. +// The Ref is resolved to a Workspace. The Workspaces PluginConfigs from the +// buf.yaml file and the PluginKeys from the buf.lock are resolved to create the +// PluginKeyProvider. +// If the Ref is a MessageRef, the current directory buf.lock file is used. +// +// PluginConfigs are validated to ensure that all remote PluginConfigs are +// pinned in the buf.lock file. func (c *controller) getPluginKeyProviderForRef( ctx context.Context, ref buffetch.Ref, functionOptions *functionOptions, ) (_ bufplugin.PluginKeyProvider, retErr error) { + var ( + pluginConfigs []bufconfig.PluginConfig + pluginKeys []bufplugin.PluginKey + ) switch t := ref.(type) { case buffetch.ProtoFileRef: workspace, err := c.getWorkspaceForProtoFileRef(ctx, t, functionOptions) if err != nil { return nil, err } - return bufplugin.NewStaticPluginKeyProvider(workspace.RemotePluginKeys()) + pluginConfigs = workspace.PluginConfigs() + pluginKeys = workspace.RemotePluginKeys() case buffetch.SourceRef: workspace, err := c.getWorkspaceForSourceRef(ctx, t, functionOptions) if err != nil { return nil, err } - return bufplugin.NewStaticPluginKeyProvider(workspace.RemotePluginKeys()) + pluginConfigs = workspace.PluginConfigs() + pluginKeys = workspace.RemotePluginKeys() case buffetch.ModuleRef: workspace, err := c.getWorkspaceForModuleRef(ctx, t, functionOptions) if err != nil { return nil, err } - return bufplugin.NewStaticPluginKeyProvider(workspace.RemotePluginKeys()) + pluginConfigs = workspace.PluginConfigs() + pluginKeys = workspace.RemotePluginKeys() case buffetch.MessageRef: bucket, err := c.storageosProvider.NewReadWriteBucket( ".", @@ -1247,10 +1260,11 @@ func (c *controller) getPluginKeyProviderForRef( } // We did not find a buf.yaml in our current directory, // and there was no config override. + // Remote plugins are not available. return bufplugin.NopPluginKeyProvider, nil } - var pluginKeys []bufplugin.PluginKey if bufYAMLFile.FileVersion() == bufconfig.FileVersionV2 { + pluginConfigs = bufYAMLFile.PluginConfigs() bufLockFile, err := bufconfig.GetBufLockFileForPrefix( ctx, bucket, @@ -1267,11 +1281,32 @@ func (c *controller) getPluginKeyProviderForRef( } pluginKeys = bufLockFile.RemotePluginKeys() } - return bufplugin.NewStaticPluginKeyProvider(pluginKeys) default: // This is a system error. return nil, syserror.Newf("invalid Ref: %T", ref) } + // Validate that all remote PluginConfigs are present in the buf.lock file. + pluginKeysByFullName, err := slicesext.ToUniqueValuesMap(pluginKeys, func(pluginKey bufplugin.PluginKey) string { + return pluginKey.FullName().String() + }) + if err != nil { + return nil, fmt.Errorf("failed to validate remote PluginKeys: %w", err) + } + // Remote PluginConfig Refs are any PluginConfigs that have a Ref. + remotePluginRefs := slicesext.Filter( + slicesext.Map(pluginConfigs, func(pluginConfig bufconfig.PluginConfig) bufparse.Ref { + return pluginConfig.Ref() + }), + func(pluginRef bufparse.Ref) bool { + return pluginRef != nil + }, + ) + for _, remotePluginRef := range remotePluginRefs { + if _, ok := pluginKeysByFullName[remotePluginRef.FullName().String()]; !ok { + return nil, fmt.Errorf(`remote plugin %q is not in the buf.lock file, use "buf plugin update" to pin remote refs`, remotePluginRef) + } + } + return bufplugin.NewStaticPluginKeyProvider(pluginKeys) } // handleFileAnnotationSetError will attempt to handle the error as a FileAnnotationSet, and if so, print diff --git a/private/buf/buflsp/file.go b/private/buf/buflsp/file.go index 6f2572ce0a..da240ab650 100644 --- a/private/buf/buflsp/file.go +++ b/private/buf/buflsp/file.go @@ -32,6 +32,7 @@ import ( "github.com/bufbuild/buf/private/bufpkg/bufcheck" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/bufpkg/bufmodule" + "github.com/bufbuild/buf/private/bufpkg/bufplugin" "github.com/bufbuild/buf/private/pkg/git" "github.com/bufbuild/buf/private/pkg/ioext" "github.com/bufbuild/buf/private/pkg/normalpath" @@ -373,7 +374,7 @@ func (f *file) FindModule(ctx context.Context) { } // Get the check runner provider for this file. The client is scoped to - // the input Buf lock file, so we need to get the check runner provider + // the inputs buf.lock file, so we need to get the check runner provider // for the workspace that contains this file. checkRunnerProvider, err := f.lsp.controller.GetCheckRunnerProvider(ctx, f.uri.Filename(), f.lsp.wasmRuntime) if err != nil { diff --git a/private/buf/bufworkspace/workspace_provider.go b/private/buf/bufworkspace/workspace_provider.go index 58152f94b3..0852af753a 100644 --- a/private/buf/bufworkspace/workspace_provider.go +++ b/private/buf/bufworkspace/workspace_provider.go @@ -405,8 +405,8 @@ func (w *workspaceProvider) getWorkspaceForBucketAndModuleDirPathsV1Beta1OrV1( return w.getWorkspaceForBucketModuleSet( moduleSet, v1WorkspaceTargeting.bucketIDToModuleConfig, - nil, // No plugin configs for v1 - nil, // No remote plugin keys for v1 + nil, // No PluginConfigs for v1 + nil, // No remote PluginKeys for v1 v1WorkspaceTargeting.allConfiguredDepModuleRefs, false, ) diff --git a/private/bufpkg/bufcheck/runner_provider.go b/private/bufpkg/bufcheck/runner_provider.go index 3e7d73c3dd..7bd3746969 100644 --- a/private/bufpkg/bufcheck/runner_provider.go +++ b/private/bufpkg/bufcheck/runner_provider.go @@ -100,7 +100,7 @@ func newRemoteWasmPluginRunner( ) (*remoteWasmPluginRunner, error) { pluginRef := pluginConfig.Ref() if pluginRef == nil { - return nil, syserror.Newf("Ref nil on PluginConfig of type %v", bufconfig.PluginConfigTypeRemoteWasm) + return nil, syserror.Newf("Ref nil on PluginConfig of type %v", pluginConfig.Type()) } return &remoteWasmPluginRunner{ wasmRuntime: wasmRuntime, diff --git a/private/bufpkg/bufplugin/plugin_key_provider.go b/private/bufpkg/bufplugin/plugin_key_provider.go index cef430f5db..0fba69f8b6 100644 --- a/private/bufpkg/bufplugin/plugin_key_provider.go +++ b/private/bufpkg/bufplugin/plugin_key_provider.go @@ -59,13 +59,8 @@ func NewStaticPluginKeyProvider(pluginKeys []PluginKey) (PluginKeyProvider, erro if err != nil { return nil, err } - digetType, err := UniqueDigestTypeForPluginKeys(pluginKeys) - if err != nil { - return nil, err - } return staticPluginKeyProvider{ pluginKeysByFullName: pluginKeysByFullName, - digestType: digetType, }, nil } @@ -83,7 +78,6 @@ func (nopPluginKeyProvider) GetPluginKeysForPluginRefs( type staticPluginKeyProvider struct { pluginKeysByFullName map[string]PluginKey - digestType DigestType } func (s staticPluginKeyProvider) GetPluginKeysForPluginRefs( @@ -91,9 +85,6 @@ func (s staticPluginKeyProvider) GetPluginKeysForPluginRefs( refs []bufparse.Ref, digestType DigestType, ) ([]PluginKey, error) { - if digestType != s.digestType { - return nil, fmt.Errorf("expected DigestType %v, got %v", s.digestType, digestType) - } pluginKeys := make([]PluginKey, len(refs)) for i, ref := range refs { // Only the FullName is used to match the PluginKey. The Ref is not @@ -103,6 +94,13 @@ func (s staticPluginKeyProvider) GetPluginKeysForPluginRefs( if !ok { return nil, fs.ErrNotExist } + digest, err := pluginKey.Digest() + if err != nil { + return nil, err + } + if digest.Type() != digestType { + return nil, fmt.Errorf("expected DigestType %v, got %v", digestType, digest.Type()) + } pluginKeys[i] = pluginKey } return pluginKeys, nil