Skip to content

Commit 2b2ead6

Browse files
authored
Add buf policy prune command (#3960)
1 parent 623a332 commit 2b2ead6

File tree

4 files changed

+198
-2
lines changed

4 files changed

+198
-2
lines changed

private/buf/bufworkspace/workspace_dep_manager.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ type WorkspaceDepManager interface {
8282
//
8383
// Sorted.
8484
ConfiguredRemotePluginRefs(ctx context.Context) ([]bufparse.Ref, error)
85+
// ConfiguredRemotePolicyRefs returns the configured remote plugins of the Workspace as PolicyRefs.
86+
//
87+
// These come from buf.yaml files.
88+
//
89+
// The PolicyRefs in this list will be unique by FullName. If there are two PolicyRefs
90+
// in the buf.yaml with the same FullName but different Refs, an error will be given
91+
// at workspace constructions.
92+
//
93+
// Sorted.
94+
ConfiguredRemotePolicyRefs(ctx context.Context) ([]bufparse.Ref, error)
8595

8696
isWorkspaceDepManager()
8797
}
@@ -188,6 +198,50 @@ func (w *workspaceDepManager) ConfiguredRemotePluginRefs(ctx context.Context) ([
188198
return pluginRefs, nil
189199
}
190200

201+
func (w *workspaceDepManager) ConfiguredRemotePolicyRefs(ctx context.Context) ([]bufparse.Ref, error) {
202+
bufYAMLFile, err := bufconfig.GetBufYAMLFileForPrefix(ctx, w.bucket, w.targetSubDirPath)
203+
if err != nil {
204+
if !errors.Is(err, fs.ErrNotExist) {
205+
return nil, err
206+
}
207+
}
208+
if bufYAMLFile == nil {
209+
return nil, nil
210+
}
211+
switch fileVersion := bufYAMLFile.FileVersion(); fileVersion {
212+
case bufconfig.FileVersionV1Beta1, bufconfig.FileVersionV1:
213+
if w.isV2 {
214+
return nil, syserror.Newf("buf.yaml at %q did had version %v but expected v1beta1, v1", w.targetSubDirPath, fileVersion)
215+
}
216+
// Policys are not supported in versions less than v2.
217+
return nil, nil
218+
case bufconfig.FileVersionV2:
219+
if !w.isV2 {
220+
return nil, syserror.Newf("buf.yaml at %q did had version %v but expected v2", w.targetSubDirPath, fileVersion)
221+
}
222+
default:
223+
return nil, syserror.Newf("unknown FileVersion: %v", fileVersion)
224+
}
225+
policyRefs := xslices.Filter(
226+
xslices.Map(
227+
bufYAMLFile.PolicyConfigs(),
228+
func(value bufconfig.PolicyConfig) bufparse.Ref {
229+
return value.Ref()
230+
},
231+
),
232+
func(value bufparse.Ref) bool {
233+
return value != nil
234+
},
235+
)
236+
sort.Slice(
237+
policyRefs,
238+
func(i int, j int) bool {
239+
return policyRefs[i].FullName().String() < policyRefs[j].FullName().String()
240+
},
241+
)
242+
return policyRefs, nil
243+
}
244+
191245
func (w *workspaceDepManager) BufLockFileDigestType() bufmodule.DigestType {
192246
if w.isV2 {
193247
return bufmodule.DigestTypeB5
@@ -239,7 +293,7 @@ func (w *workspaceDepManager) ExistingBufLockFilePolicyNameToRemotePluginKeys(ct
239293
return bufLockFile.PolicyNameToRemotePluginKeys(), nil
240294
}
241295

242-
func (w *workspaceDepManager) UpdateBufLockFile(ctx context.Context, depModuleKeys []bufmodule.ModuleKey, remotePluginKeys []bufplugin.PluginKey, remotePolicyKeys []bufpolicy.PolicyKey, policyNameToRemotePolicyKeys map[string][]bufplugin.PluginKey) error {
296+
func (w *workspaceDepManager) UpdateBufLockFile(ctx context.Context, depModuleKeys []bufmodule.ModuleKey, remotePluginKeys []bufplugin.PluginKey, remotePolicyKeys []bufpolicy.PolicyKey, policyNameToRemotePluginKeys map[string][]bufplugin.PluginKey) error {
243297
var bufLockFile bufconfig.BufLockFile
244298
var err error
245299
if w.isV2 {
@@ -248,7 +302,7 @@ func (w *workspaceDepManager) UpdateBufLockFile(ctx context.Context, depModuleKe
248302
depModuleKeys,
249303
remotePluginKeys,
250304
remotePolicyKeys,
251-
policyNameToRemotePolicyKeys,
305+
policyNameToRemotePluginKeys,
252306
)
253307
if err != nil {
254308
return err

private/buf/cmd/buf/buf.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
"github.com/bufbuild/buf/private/buf/cmd/buf/command/plugin/pluginprune"
6868
"github.com/bufbuild/buf/private/buf/cmd/buf/command/plugin/pluginpush"
6969
"github.com/bufbuild/buf/private/buf/cmd/buf/command/plugin/pluginupdate"
70+
"github.com/bufbuild/buf/private/buf/cmd/buf/command/policy/policyprune"
7071
"github.com/bufbuild/buf/private/buf/cmd/buf/command/policy/policypush"
7172
"github.com/bufbuild/buf/private/buf/cmd/buf/command/push"
7273
"github.com/bufbuild/buf/private/buf/cmd/buf/command/registry/module/modulecommit/modulecommitaddlabel"
@@ -204,6 +205,7 @@ func NewRootCommand(name string) *appcmd.Command {
204205
Hidden: true,
205206
SubCommands: []*appcmd.Command{
206207
policypush.NewCommand("push", builder),
208+
policyprune.NewCommand("prune", builder),
207209
},
208210
},
209211
{
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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 policyprune
16+
17+
import (
18+
"context"
19+
20+
"buf.build/go/app/appcmd"
21+
"buf.build/go/app/appext"
22+
"buf.build/go/standard/xslices"
23+
"github.com/bufbuild/buf/private/buf/bufcli"
24+
"github.com/bufbuild/buf/private/buf/bufworkspace"
25+
"github.com/bufbuild/buf/private/bufpkg/bufparse"
26+
"github.com/bufbuild/buf/private/bufpkg/bufplugin"
27+
"github.com/bufbuild/buf/private/bufpkg/bufpolicy"
28+
)
29+
30+
// NewCommand returns a new Command.
31+
func NewCommand(
32+
name string,
33+
builder appext.SubCommandBuilder,
34+
) *appcmd.Command {
35+
return &appcmd.Command{
36+
Use: name + " <directory>",
37+
Short: "Prune unused policies from buf.lock",
38+
Long: `Policies that are no longer configured in buf.yaml are removed from the buf.lock file.
39+
40+
The first argument is the directory of your buf.yaml configuration file.
41+
Defaults to "." if no argument is specified.`,
42+
Args: appcmd.MaximumNArgs(1),
43+
Run: builder.NewRunFunc(
44+
func(ctx context.Context, container appext.Container) error {
45+
return run(ctx, container)
46+
},
47+
),
48+
}
49+
}
50+
51+
func run(
52+
ctx context.Context,
53+
container appext.Container,
54+
) error {
55+
dirPath := "."
56+
if container.NumArgs() > 0 {
57+
dirPath = container.Arg(0)
58+
}
59+
controller, err := bufcli.NewController(container)
60+
if err != nil {
61+
return err
62+
}
63+
workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath)
64+
if err != nil {
65+
return err
66+
}
67+
configuredRemotePolicyRefs, err := workspaceDepManager.ConfiguredRemotePolicyRefs(ctx)
68+
if err != nil {
69+
return err
70+
}
71+
return prune(
72+
ctx,
73+
xslices.Map(
74+
configuredRemotePolicyRefs,
75+
func(policyRef bufparse.Ref) string {
76+
return policyRef.FullName().String()
77+
},
78+
),
79+
workspaceDepManager,
80+
)
81+
}
82+
83+
func prune(
84+
ctx context.Context,
85+
bufYAMLBasedRemotePolicyNames []string,
86+
workspaceDepManager bufworkspace.WorkspaceDepManager,
87+
) error {
88+
bufYAMLRemotePolicyNames := xslices.ToStructMap(bufYAMLBasedRemotePolicyNames)
89+
existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx)
90+
if err != nil {
91+
return err
92+
}
93+
existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx)
94+
if err != nil {
95+
return err
96+
}
97+
var (
98+
prunedBufLockPolicyKeys []bufpolicy.PolicyKey
99+
prunedBufLockPolicyNameToRemotePluginKeys = make(map[string][]bufplugin.PluginKey)
100+
)
101+
for _, existingRemotePolicyKey := range existingRemotePolicyKeys {
102+
// Check if an existing policy key from the buf.lock is configured in the buf.yaml.
103+
policyFullNameString := existingRemotePolicyKey.FullName().String()
104+
if _, ok := bufYAMLRemotePolicyNames[policyFullNameString]; ok {
105+
// If yes, then we keep it for the updated buf.lock.
106+
prunedBufLockPolicyKeys = append(prunedBufLockPolicyKeys, existingRemotePolicyKey)
107+
existingRemotePluginKeys := existingPolicyNameToRemotePluginKeys[policyFullNameString]
108+
prunedBufLockPolicyNameToRemotePluginKeys[policyFullNameString] = existingRemotePluginKeys
109+
}
110+
}
111+
// We keep the existing dep module keys as-is.
112+
existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx)
113+
if err != nil {
114+
return err
115+
}
116+
existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx)
117+
if err != nil {
118+
return err
119+
}
120+
return workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, existingRemotePluginKeys, prunedBufLockPolicyKeys, prunedBufLockPolicyNameToRemotePluginKeys)
121+
}

private/buf/cmd/buf/command/policy/policyprune/usage.gen.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)