Skip to content

Commit 07b899e

Browse files
authored
Add buf policy YAML file config (#3769)
1 parent 45e582f commit 07b899e

File tree

15 files changed

+1028
-5
lines changed

15 files changed

+1028
-5
lines changed

private/buf/bufmigrate/migrator.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,8 @@ func (m *migrator) buildBufYAMLAndBufLockFiles(
380380
bufYAML, err := bufconfig.NewBufYAMLFile(
381381
bufconfig.FileVersionV2,
382382
migrateBuilder.moduleConfigs,
383-
// TODO: If we ever need to migrate from a v2 to v3, we will need to handle PluginConfigs
383+
// TODO: If we ever need to migrate from a v2 to v3, we will need to handle PluginConfigs and PolicyConfigs
384+
nil,
384385
nil,
385386
resolvedDeclaredRefs,
386387
)
@@ -429,7 +430,8 @@ func (m *migrator) buildBufYAMLAndBufLockFiles(
429430
bufYAML, err := bufconfig.NewBufYAMLFile(
430431
bufconfig.FileVersionV2,
431432
migrateBuilder.moduleConfigs,
432-
// TODO: If we ever need to migrate from a v2 to v3, we will need to handle PluginConfigs
433+
// TODO: If we ever need to migrate from a v2 to v3, we will need to handle PluginConfigs and PolicyConfigs
434+
nil,
433435
nil,
434436
resolvedDepModuleRefs,
435437
)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ func run(
175175
},
176176
nil,
177177
nil,
178+
nil,
178179
bufconfig.BufYAMLFileWithIncludeDocsLink(),
179180
)
180181
if err != nil {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func getExternalModules(
143143
},
144144
nil,
145145
nil,
146+
nil,
146147
)
147148
if err != nil {
148149
return nil, err

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ func lsRun(
177177
},
178178
nil,
179179
nil,
180+
nil,
180181
)
181182
if err != nil {
182183
return err

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func lsRun(
162162
},
163163
nil,
164164
nil,
165+
nil,
165166
)
166167
if err != nil {
167168
return err

private/bufpkg/bufconfig/buf_yaml_file.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ type BufYAMLFile interface {
105105
//
106106
// For v1 buf.yaml files, this will always return nil.
107107
PluginConfigs() []PluginConfig
108+
// PolicyConfigs returns the PolicyConfigs for the File.
109+
//
110+
// For v1 buf.yaml files, this will always return nil.
111+
PolicyConfigs() []PolicyConfig
108112
// ConfiguredDepModuleRefs returns the configured dependencies of the Workspace as ModuleRefs.
109113
//
110114
// These come from buf.yaml files.
@@ -126,6 +130,7 @@ func NewBufYAMLFile(
126130
fileVersion FileVersion,
127131
moduleConfigs []ModuleConfig,
128132
pluginConfigs []PluginConfig,
133+
policyConfigs []PolicyConfig,
129134
configuredDepModuleRefs []bufparse.Ref,
130135
options ...BufYAMLFileOption,
131136
) (BufYAMLFile, error) {
@@ -140,6 +145,7 @@ func NewBufYAMLFile(
140145
nil, // Do not set top-level lint config, use only module configs
141146
nil, // Do not set top-level breaking config, use only module configs
142147
pluginConfigs,
148+
policyConfigs,
143149
configuredDepModuleRefs,
144150
bufYAMLFileOptions.includeDocsLink,
145151
)
@@ -254,6 +260,7 @@ type bufYAMLFile struct {
254260
topLevelLintConfig LintConfig
255261
topLevelBreakingConfig BreakingConfig
256262
pluginConfigs []PluginConfig
263+
policyConfigs []PolicyConfig
257264
configuredDepModuleRefs []bufparse.Ref
258265
includeDocsLink bool
259266
}
@@ -265,6 +272,7 @@ func newBufYAMLFile(
265272
topLevelLintConfig LintConfig,
266273
topLevelBreakingConfig BreakingConfig,
267274
pluginConfigs []PluginConfig,
275+
policyConfigs []PolicyConfig,
268276
configuredDepModuleRefs []bufparse.Ref,
269277
includeDocsLink bool,
270278
) (*bufYAMLFile, error) {
@@ -320,6 +328,7 @@ func newBufYAMLFile(
320328
topLevelLintConfig: topLevelLintConfig,
321329
topLevelBreakingConfig: topLevelBreakingConfig,
322330
pluginConfigs: pluginConfigs,
331+
policyConfigs: policyConfigs,
323332
configuredDepModuleRefs: configuredDepModuleRefs,
324333
includeDocsLink: includeDocsLink,
325334
}, nil
@@ -353,6 +362,10 @@ func (c *bufYAMLFile) PluginConfigs() []PluginConfig {
353362
return c.pluginConfigs
354363
}
355364

365+
func (c *bufYAMLFile) PolicyConfigs() []PolicyConfig {
366+
return c.policyConfigs
367+
}
368+
356369
func (c *bufYAMLFile) ConfiguredDepModuleRefs() []bufparse.Ref {
357370
return slices.Clone(c.configuredDepModuleRefs)
358371
}
@@ -455,6 +468,7 @@ func readBufYAMLFile(
455468
lintConfig,
456469
breakingConfig,
457470
nil,
471+
nil,
458472
configuredDepModuleRefs,
459473
includeDocsLink,
460474
)
@@ -651,6 +665,14 @@ func readBufYAMLFile(
651665
}
652666
pluginConfigs = append(pluginConfigs, pluginConfig)
653667
}
668+
var policyConfigs []PolicyConfig
669+
for _, externalPolicyConfig := range externalBufYAMLFile.Policies {
670+
policyConfig, err := newPolicyConfigForExternalV2(externalPolicyConfig)
671+
if err != nil {
672+
return nil, err
673+
}
674+
policyConfigs = append(policyConfigs, policyConfig)
675+
}
654676
configuredDepModuleRefs, err := getConfiguredDepModuleRefsForExternalDeps(externalBufYAMLFile.Deps)
655677
if err != nil {
656678
return nil, err
@@ -662,6 +684,7 @@ func readBufYAMLFile(
662684
topLevelLintConfig,
663685
topLevelBreakingConfig,
664686
pluginConfigs,
687+
policyConfigs,
665688
configuredDepModuleRefs,
666689
includeDocsLink,
667690
)
@@ -861,6 +884,16 @@ func writeBufYAMLFile(writer io.Writer, bufYAMLFile BufYAMLFile) error {
861884
}
862885
externalBufYAMLFile.Plugins = externalPlugins
863886

887+
var externalPolicies []externalBufYAMLFilePolicyV2
888+
for _, policyConfig := range bufYAMLFile.PolicyConfigs() {
889+
externalPolicy, err := newExternalV2ForPolicyConfig(policyConfig)
890+
if err != nil {
891+
return syserror.Wrap(err)
892+
}
893+
externalPolicies = append(externalPolicies, externalPolicy)
894+
}
895+
externalBufYAMLFile.Policies = externalPolicies
896+
864897
data, err := encoding.MarshalYAML(&externalBufYAMLFile)
865898
if err != nil {
866899
return err
@@ -1272,6 +1305,7 @@ type externalBufYAMLFileV2 struct {
12721305
Lint externalBufYAMLFileLintV2 `json:"lint,omitempty" yaml:"lint,omitempty"`
12731306
Breaking externalBufYAMLFileBreakingV1Beta1V1V2 `json:"breaking,omitempty" yaml:"breaking,omitempty"`
12741307
Plugins []externalBufYAMLFilePluginV2 `json:"plugins,omitempty" yaml:"plugins,omitempty"`
1308+
Policies []externalBufYAMLFilePolicyV2 `json:"policies,omitempty" yaml:"policies,omitempty"`
12751309
}
12761310

12771311
// externalBufYAMLFileModuleV2 represents a single module configuration within a v2 buf.yaml file.
@@ -1302,7 +1336,7 @@ type externalBufYAMLFileLintV1Beta1V1 struct {
13021336
Except []string `json:"except,omitempty" yaml:"except,omitempty"`
13031337
// Ignore are the paths to ignore.
13041338
Ignore []string `json:"ignore,omitempty" yaml:"ignore,omitempty"`
1305-
/// IgnoreOnly are the ID/category to paths to ignore.
1339+
// IgnoreOnly are the ID/category to paths to ignore.
13061340
IgnoreOnly map[string][]string `json:"ignore_only,omitempty" yaml:"ignore_only,omitempty"`
13071341
EnumZeroValueSuffix string `json:"enum_zero_value_suffix,omitempty" yaml:"enum_zero_value_suffix,omitempty"`
13081342
RPCAllowSameRequestResponse bool `json:"rpc_allow_same_request_response,omitempty" yaml:"rpc_allow_same_request_response,omitempty"`
@@ -1389,12 +1423,21 @@ func (eb externalBufYAMLFileBreakingV1Beta1V1V2) isEmpty() bool {
13891423
!eb.DisableBuiltin
13901424
}
13911425

1392-
// externalBufYAMLFilePluginV2 represents a single plugin config in a v2 buf.gyaml file.
1426+
// externalBufYAMLFilePluginV2 represents a single plugin config in a v2 buf.yaml file.
13931427
type externalBufYAMLFilePluginV2 struct {
13941428
Plugin any `json:"plugin,omitempty" yaml:"plugin,omitempty"`
13951429
Options map[string]any `json:"options,omitempty" yaml:"options,omitempty"`
13961430
}
13971431

1432+
// externalBufYAMLFilePolicyV2 represents a single policy config in a v2 buf.yaml file.
1433+
type externalBufYAMLFilePolicyV2 struct {
1434+
Policy string `json:"policy,omitempty" yaml:"policy,omitempty"`
1435+
// Ignore are the paths to ignore.
1436+
Ignore []string `json:"ignore,omitempty" yaml:"ignore,omitempty"`
1437+
// IgnoreOnly are the ID/category to paths to ignore.
1438+
IgnoreOnly map[string][]string `json:"ignore_only,omitempty" yaml:"ignore_only,omitempty"`
1439+
}
1440+
13981441
func getZeroOrSingleValueForMap[K comparable, V any](m map[K]V) (V, error) {
13991442
var zero V
14001443
if len(m) > 1 {

private/bufpkg/bufconfig/buf_yaml_file_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,31 @@ modules:
478478
excludes:
479479
- proto/bar
480480
- proto/foo
481+
`,
482+
)
483+
testReadWriteBufYAMLFileRoundTrip(
484+
t,
485+
// input
486+
`version: v2
487+
policies:
488+
- policy: buf.policy.yaml
489+
- policy: buf.build/org/policy2:v1
490+
ignore:
491+
- foo/bar.proto
492+
ignore_only:
493+
ENUM_PASCAL_CASE:
494+
- foo/foo.proto
495+
`,
496+
// expected output
497+
`version: v2
498+
policies:
499+
- policy: buf.policy.yaml
500+
- policy: buf.build/org/policy2:v1
501+
ignore:
502+
- foo/bar.proto
503+
ignore_only:
504+
ENUM_PASCAL_CASE:
505+
- foo/foo.proto
481506
`,
482507
)
483508
}

private/bufpkg/bufconfig/file.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func readFile[F File](
151151
var f F
152152
return f, err
153153
}
154-
f, err := readFileFunc(data, nil, true)
154+
f, err := readFileFunc(data, newObjectData(fileName, data), true)
155155
if err != nil {
156156
return f, newDecodeError(fileName, err)
157157
}

private/bufpkg/bufconfig/object_data.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ type ObjectData interface {
4040
isObjectData()
4141
}
4242

43+
// NewObjectData returns a new ObjectData.
44+
func NewObjectData(name string, data []byte) ObjectData {
45+
return newObjectData(name, data)
46+
}
47+
4348
// GetBufYAMLV1Beta1OrV1ObjectDataForPrefix is a helper function that gets the ObjectData for the buf.yaml file at
4449
// the given bucket prefix, if the buf.yaml file was v1beta1 or v1.
4550
//
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bufconfig
16+
17+
import (
18+
"errors"
19+
"slices"
20+
21+
"github.com/bufbuild/buf/private/pkg/slicesext"
22+
"github.com/bufbuild/buf/private/pkg/syserror"
23+
)
24+
25+
// PluginConfig is a configuration for a policy.
26+
type PolicyConfig interface {
27+
// Name returns the policy name. This is never empty.
28+
Name() string
29+
// Paths are specific to the Module. Users cannot ignore paths outside of their modules for check
30+
// configs, which includes any imports from outside of the module.
31+
// Paths are relative to roots.
32+
// Paths are sorted.
33+
IgnorePaths() []string
34+
// Paths are specific to the Module. Users cannot ignore paths outside of their modules for
35+
// check configs, which includes any imports from outside of the module.
36+
// Paths are relative to roots.
37+
// Paths are sorted.
38+
IgnoreIDOrCategoryToPaths() map[string][]string
39+
40+
isPolicyConfig()
41+
}
42+
43+
// NewPolicyConfig returns a new PolicyConfig.
44+
func NewPolicyConfig(
45+
name string,
46+
ignore []string,
47+
ignoreOnly map[string][]string,
48+
) (PolicyConfig, error) {
49+
return newPolicyConfig(name, ignore, ignoreOnly)
50+
}
51+
52+
// *** PRIVATE ***
53+
54+
type policyConfig struct {
55+
name string
56+
ignore []string
57+
ignoreOnly map[string][]string
58+
}
59+
60+
func newPolicyConfigForExternalV2(
61+
externalConfig externalBufYAMLFilePolicyV2,
62+
) (*policyConfig, error) {
63+
return newPolicyConfig(
64+
externalConfig.Policy,
65+
externalConfig.Ignore,
66+
externalConfig.IgnoreOnly,
67+
)
68+
}
69+
70+
func newPolicyConfig(
71+
name string,
72+
ignore []string,
73+
ignoreOnly map[string][]string,
74+
) (*policyConfig, error) {
75+
if name == "" {
76+
return nil, errors.New("must specify a name to the policy")
77+
}
78+
ignore = slicesext.ToUniqueSorted(ignore)
79+
ignore, err := normalizeAndCheckPaths(ignore, "ignore")
80+
if err != nil {
81+
return nil, err
82+
}
83+
newIgnoreOnly := make(map[string][]string, len(ignoreOnly))
84+
for k, v := range ignoreOnly {
85+
v = slicesext.ToUniqueSorted(v)
86+
v, err := normalizeAndCheckPaths(v, "ignore_only path")
87+
if err != nil {
88+
return nil, err
89+
}
90+
newIgnoreOnly[k] = v
91+
}
92+
ignoreOnly = newIgnoreOnly
93+
return &policyConfig{
94+
name: name,
95+
ignore: ignore,
96+
ignoreOnly: ignoreOnly,
97+
}, nil
98+
}
99+
100+
func (p *policyConfig) Name() string {
101+
return p.name
102+
}
103+
104+
func (p *policyConfig) IgnorePaths() []string {
105+
return slices.Clone(p.ignore)
106+
}
107+
108+
func (p *policyConfig) IgnoreIDOrCategoryToPaths() map[string][]string {
109+
return copyStringToStringSliceMap(p.ignoreOnly)
110+
}
111+
112+
func (p *policyConfig) isPolicyConfig() {}
113+
114+
func newExternalV2ForPolicyConfig(
115+
config PolicyConfig,
116+
) (externalBufYAMLFilePolicyV2, error) {
117+
pluginConfig, ok := config.(*policyConfig)
118+
if !ok {
119+
return externalBufYAMLFilePolicyV2{}, syserror.Newf("unknown implementation of PolicyConfig: %T", pluginConfig)
120+
}
121+
return externalBufYAMLFilePolicyV2{
122+
Policy: pluginConfig.Name(),
123+
Ignore: slices.Clone(pluginConfig.IgnorePaths()),
124+
IgnoreOnly: copyStringToStringSliceMap(pluginConfig.IgnoreIDOrCategoryToPaths()),
125+
}, nil
126+
}

0 commit comments

Comments
 (0)