Skip to content

Commit 70f3738

Browse files
authored
Add policy support to buf config commands (#3983)
1 parent 63d995b commit 70f3738

File tree

11 files changed

+216
-56
lines changed

11 files changed

+216
-56
lines changed

private/buf/cmd/buf/buf_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,23 @@ PACKAGE_NO_IMPORT_CYCLE Checks that
715715
)
716716
}
717717

718+
func TestCheckPolicyConfigLsLintRulesConfigured(t *testing.T) {
719+
t.Parallel()
720+
expectedConfiguredOnlyStdout, err := os.ReadFile(filepath.Join("testdata", "policy_list_rules", "expected_ls_lint_rules_configured_only.txt"))
721+
require.NoError(t, err)
722+
testRunStdout(
723+
t,
724+
nil,
725+
0,
726+
string(expectedConfiguredOnlyStdout),
727+
"config",
728+
"ls-lint-rules",
729+
"--config",
730+
filepath.Join("testdata", "policy_list_rules", "buf.yaml"),
731+
"--configured-only",
732+
)
733+
}
734+
718735
func TestCheckLsLintRulesFromConfig(t *testing.T) {
719736
t.Parallel()
720737
testRunStdout(

private/buf/cmd/buf/command/config/internal/internal.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ func lsRun(
257257
}
258258
configuredRuleOptions := []bufcheck.ConfiguredRulesOption{
259259
bufcheck.WithPluginConfigs(bufYAMLFile.PluginConfigs()...),
260+
bufcheck.WithPolicyConfigs(bufYAMLFile.PolicyConfigs()...),
260261
bufcheck.WithRelatedCheckConfigs(allCheckConfigs...),
261262
}
262263
rules, err = checkClient.ConfiguredRules(
@@ -271,6 +272,7 @@ func lsRun(
271272
} else {
272273
allRulesOptions := []bufcheck.AllRulesOption{
273274
bufcheck.WithPluginConfigs(bufYAMLFile.PluginConfigs()...),
275+
bufcheck.WithPolicyConfigs(bufYAMLFile.PolicyConfigs()...),
274276
}
275277
rules, err = checkClient.AllRules(
276278
ctx,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: v2
2+
lint:
3+
use:
4+
- ENUM_NO_ALLOW_ALIAS
5+
- PACKAGE_DIRECTORY_MATCH
6+
breaking:
7+
use:
8+
- ENUM_VALUE_NO_DELETE
9+
- FIELD_SAME_JSTYPE
10+
policies:
11+
- policy: testdata/policy_list_rules/policy.yaml
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
ID CATEGORIES DEFAULT PURPOSE
2+
PACKAGE_DIRECTORY_MATCH MINIMAL, BASIC, STANDARD * Checks that all files are in a directory that matches their package name.
3+
ENUM_NO_ALLOW_ALIAS BASIC, STANDARD * Checks that enums do not have the allow_alias option set.
4+
5+
testdata/policy_list_rules/policy.yaml
6+
7+
PACKAGE_DIRECTORY_MATCH MINIMAL, BASIC, STANDARD * Checks that all files are in a directory that matches their package name.
8+
ENUM_NO_ALLOW_ALIAS BASIC, STANDARD * Checks that enums do not have the allow_alias option set.
9+
10+
testdata/policy_list_rules/policy.yaml buf-plugin-rpc-ext
11+
12+
PAGE_REQUEST_HAS_TOKEN * Checks that all pagination RPC requests has a page token set.
13+
PAGE_RESPONSE_HAS_TOKEN * Checks that all pagination RPC responses has a page token set.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: v2
2+
lint:
3+
use:
4+
- ENUM_NO_ALLOW_ALIAS
5+
- PACKAGE_DIRECTORY_MATCH
6+
- PAGE_REQUEST_HAS_TOKEN
7+
- PAGE_RESPONSE_HAS_TOKEN
8+
breaking:
9+
use:
10+
- ENUM_VALUE_NO_DELETE
11+
- FIELD_SAME_JSTYPE
12+
plugins:
13+
- plugin: buf-plugin-rpc-ext

private/bufpkg/bufcheck/bufcheck.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ type Rule interface {
7474
//
7575
// Will be empty for Rules based on builtin plugins.
7676
PluginName() string
77+
// PolicyName returns the name of the policy that contains this Rule, if any.
78+
//
79+
// Names are freeform.
80+
//
81+
// Will be empty for Rules not based on policies.
82+
PolicyName() string
7783

7884
isRule()
7985
isRuleOrCategory()
@@ -91,6 +97,12 @@ type Category interface {
9197
//
9298
// Will be empty for Categorys based on builtin plugins.
9399
PluginName() string
100+
// PolicyName returns the name of the policy that contains this Category, if any.
101+
//
102+
// Names are freeform.
103+
//
104+
// Will be empty for Categorys not based on policies.
105+
PolicyName() string
94106

95107
isCategory()
96108
isRuleOrCategory()

private/bufpkg/bufcheck/category.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,24 @@ type category struct {
2424
check.Category
2525

2626
pluginName string
27+
policyName string
2728
}
2829

29-
func newCategory(checkCategory check.Category, pluginName string) *category {
30+
func newCategory(checkCategory check.Category, pluginName string, policyName string) *category {
3031
return &category{
3132
Category: checkCategory,
3233
pluginName: pluginName,
34+
policyName: policyName,
3335
}
3436
}
3537

3638
func (r *category) PluginName() string {
3739
return r.pluginName
3840
}
3941

42+
func (r *category) PolicyName() string {
43+
return r.policyName
44+
}
45+
4046
func (*category) isCategory() {}
4147
func (*category) isRuleOrCategory() {}

private/bufpkg/bufcheck/client.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ package bufcheck
1717
import (
1818
"bytes"
1919
"context"
20+
"errors"
2021
"fmt"
2122
"io"
23+
"io/fs"
2224
"log/slog"
2325
"strings"
2426

@@ -386,7 +388,7 @@ func (c *client) ConfiguredRules(
386388
for _, option := range options {
387389
option.applyToConfiguredRules(configuredRulesOptions)
388390
}
389-
allRules, allCategories, err := c.allRulesAndCategories(
391+
rules, categories, err := c.allRulesAndCategories(
390392
ctx,
391393
checkConfig.FileVersion(),
392394
configuredRulesOptions.pluginConfigs,
@@ -396,12 +398,46 @@ func (c *client) ConfiguredRules(
396398
if err != nil {
397399
return nil, err
398400
}
399-
rulesConfig, err := rulesConfigForCheckConfig(checkConfig, allRules, allCategories, ruleType, configuredRulesOptions.relatedCheckConfigs)
401+
rulesConfig, err := rulesConfigForCheckConfig(checkConfig, rules, categories, ruleType, configuredRulesOptions.relatedCheckConfigs)
400402
if err != nil {
401403
return nil, err
402404
}
405+
allRules := rulesForRuleIDs(rules, rulesConfig.RuleIDs)
406+
policies, err := c.getPolicies(ctx, configuredRulesOptions.policyConfigs)
407+
if err != nil {
408+
return nil, err
409+
}
410+
for index, policy := range policies {
411+
policyConfig := configuredRulesOptions.policyConfigs[index]
412+
pluginConfigs, err := policyToBufConfigPluginConfigs(policy)
413+
if err != nil {
414+
return nil, err
415+
}
416+
// Load the check config for the rule type.
417+
var policyCheckConfig bufconfig.CheckConfig
418+
switch ruleType {
419+
case check.RuleTypeLint:
420+
policyCheckConfig, err = policyToBufConfigLintConfig(policy, policyConfig)
421+
case check.RuleTypeBreaking:
422+
policyCheckConfig, err = policyToBufConfigBreakingConfig(policy, policyConfig)
423+
default:
424+
return nil, fmt.Errorf("unknown check.RuleType: %v", ruleType)
425+
}
426+
if err != nil {
427+
return nil, err
428+
}
429+
policyRules, policyCategories, err := c.allRulesAndCategories(ctx, policyCheckConfig.FileVersion(), pluginConfigs, policyConfig, false)
430+
if err != nil {
431+
return nil, err
432+
}
433+
policyRulesConfig, err := rulesConfigForCheckConfig(policyCheckConfig, policyRules, policyCategories, ruleType, nil)
434+
if err != nil {
435+
return nil, err
436+
}
437+
allRules = append(allRules, rulesForRuleIDs(policyRules, policyRulesConfig.RuleIDs)...)
438+
}
403439
logRulesConfig(c.logger, rulesConfig)
404-
return rulesForRuleIDs(allRules, rulesConfig.RuleIDs), nil
440+
return allRules, nil
405441
}
406442

407443
func (c *client) AllRules(
@@ -420,6 +456,22 @@ func (c *client) AllRules(
420456
if err != nil {
421457
return nil, err
422458
}
459+
policies, err := c.getPolicies(ctx, allRulesOptions.policyConfigs)
460+
if err != nil {
461+
return nil, err
462+
}
463+
for index, policy := range policies {
464+
policyConfig := allRulesOptions.policyConfigs[index]
465+
pluginConfigs, err := policyToBufConfigPluginConfigs(policy)
466+
if err != nil {
467+
return nil, err
468+
}
469+
policyRules, _, err := c.allRulesAndCategories(ctx, fileVersion, pluginConfigs, policyConfig, false)
470+
if err != nil {
471+
return nil, err
472+
}
473+
rules = append(rules, policyRules...)
474+
}
423475
return rulesForType(rules, ruleType), nil
424476
}
425477

@@ -580,6 +632,12 @@ func (c *client) getPlugins(ctx context.Context, pluginConfigs []bufconfig.Plugi
580632
pluginRefs := xslices.IndexedToValues(indexedPluginRefs)
581633
pluginKeys, err := pluginKeyProvider.GetPluginKeysForPluginRefs(ctx, pluginRefs, bufplugin.DigestTypeP1)
582634
if err != nil {
635+
if errors.Is(err, fs.ErrNotExist) {
636+
if policyConfig != nil {
637+
return nil, fmt.Errorf("unable to resolve plugins for policy %q: %w", policyConfig.Name(), err)
638+
}
639+
return nil, fmt.Errorf("unable to resolve plugins: %w", err)
640+
}
583641
return nil, err
584642
}
585643
pluginDatas, err := pluginDataProvider.GetPluginDatasForPluginKeys(ctx, pluginKeys)

private/bufpkg/bufcheck/multi_client.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,7 @@ func (c *multiClient) Check(ctx context.Context, request check.Request) ([]*anno
9797
defer xslog.DebugProfile(c.logger, slog.String("plugin", delegate.PluginName))()
9898
delegateResponse, err := delegate.Client.Check(ctx, delegateRequest)
9999
if err != nil {
100-
if delegate.PluginName == "" {
101-
return err
102-
}
103-
return fmt.Errorf("plugin %q failed: %w", delegate.PluginName, err)
100+
return formatDelegateError(delegate, err)
104101
}
105102
annotations := xslices.Map(
106103
delegateResponse.Annotations(),
@@ -155,14 +152,13 @@ func (c *multiClient) getRulesCategoriesAndChunkedIDs(ctx context.Context) (
155152
for i, delegate := range c.checkClientSpecs {
156153
delegateCheckRules, err := delegate.Client.ListRules(ctx)
157154
if err != nil {
158-
if delegate.PluginName == "" {
159-
return nil, nil, nil, nil, err
160-
}
161-
return nil, nil, nil, nil, fmt.Errorf("plugin %q failed: %w", delegate.PluginName, err)
155+
return nil, nil, nil, nil, formatDelegateError(delegate, err)
162156
}
163157
delegateRules := xslices.Map(
164158
delegateCheckRules,
165-
func(checkRule check.Rule) Rule { return newRule(checkRule, delegate.PluginName) },
159+
func(checkRule check.Rule) Rule {
160+
return newRule(checkRule, delegate.PluginName, delegate.PolicyName)
161+
},
166162
)
167163
rules = append(rules, delegateRules...)
168164
// Already sorted.
@@ -174,14 +170,13 @@ func (c *multiClient) getRulesCategoriesAndChunkedIDs(ctx context.Context) (
174170
for i, delegate := range c.checkClientSpecs {
175171
delegateCheckCategories, err := delegate.Client.ListCategories(ctx)
176172
if err != nil {
177-
if delegate.PluginName == "" {
178-
return nil, nil, nil, nil, err
179-
}
180-
return nil, nil, nil, nil, fmt.Errorf("plugin %q failed: %w", delegate.PluginName, err)
173+
return nil, nil, nil, nil, formatDelegateError(delegate, err)
181174
}
182175
delegateCategories := xslices.Map(
183176
delegateCheckCategories,
184-
func(checkCategory check.Category) Category { return newCategory(checkCategory, delegate.PluginName) },
177+
func(checkCategory check.Category) Category {
178+
return newCategory(checkCategory, delegate.PluginName, delegate.PolicyName)
179+
},
185180
)
186181
categories = append(categories, delegateCategories...)
187182
// Already sorted.
@@ -300,3 +295,19 @@ func (d *duplicateRuleOrCategoryError) duplicateIDs() []string {
300295
}
301296
return xslices.MapKeysToSortedSlice(d.duplicateIDToRuleOrCategories)
302297
}
298+
299+
// formatDelegateError formats the error messages including plugin and policy information.
300+
func formatDelegateError(delegate *checkClientSpec, err error) error {
301+
if delegate.PluginName == "" {
302+
return err // Built-in rule, return error as-is
303+
}
304+
var errorMsg strings.Builder
305+
errorMsg.WriteString("plugin ")
306+
errorMsg.WriteString(fmt.Sprintf("%q", delegate.PluginName))
307+
if delegate.PolicyName != "" {
308+
errorMsg.WriteString(" from policy ")
309+
errorMsg.WriteString(fmt.Sprintf("%q", delegate.PolicyName))
310+
}
311+
errorMsg.WriteString(" failed: ")
312+
return fmt.Errorf("%s%w", errorMsg.String(), err)
313+
}

0 commit comments

Comments
 (0)