Skip to content

Commit 1b2a518

Browse files
authored
Add buf policy update command (#3959)
1 parent 2b2ead6 commit 1b2a518

File tree

4 files changed

+279
-0
lines changed

4 files changed

+279
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 bufcli
16+
17+
import (
18+
"buf.build/go/app/appext"
19+
"github.com/bufbuild/buf/private/bufpkg/bufpolicy"
20+
"github.com/bufbuild/buf/private/bufpkg/bufpolicy/bufpolicyapi"
21+
"github.com/bufbuild/buf/private/bufpkg/bufregistryapi/bufregistryapipolicy"
22+
)
23+
24+
// NewPolicyKeyProvider returns a new PolicyKeyProvider.
25+
func NewPolicyKeyProvider(container appext.Container) (bufpolicy.PolicyKeyProvider, error) {
26+
clientConfig, err := NewConnectClientConfig(container)
27+
if err != nil {
28+
return nil, err
29+
}
30+
return bufpolicyapi.NewPolicyKeyProvider(
31+
container.Logger(),
32+
bufregistryapipolicy.NewClientProvider(
33+
clientConfig,
34+
),
35+
), nil
36+
}

private/buf/cmd/buf/buf.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import (
6969
"github.com/bufbuild/buf/private/buf/cmd/buf/command/plugin/pluginupdate"
7070
"github.com/bufbuild/buf/private/buf/cmd/buf/command/policy/policyprune"
7171
"github.com/bufbuild/buf/private/buf/cmd/buf/command/policy/policypush"
72+
"github.com/bufbuild/buf/private/buf/cmd/buf/command/policy/policyupdate"
7273
"github.com/bufbuild/buf/private/buf/cmd/buf/command/push"
7374
"github.com/bufbuild/buf/private/buf/cmd/buf/command/registry/module/modulecommit/modulecommitaddlabel"
7475
"github.com/bufbuild/buf/private/buf/cmd/buf/command/registry/module/modulecommit/modulecommitinfo"
@@ -205,6 +206,7 @@ func NewRootCommand(name string) *appcmd.Command {
205206
Hidden: true,
206207
SubCommands: []*appcmd.Command{
207208
policypush.NewCommand("push", builder),
209+
policyupdate.NewCommand("update", builder),
208210
policyprune.NewCommand("prune", builder),
209211
},
210212
},
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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 policyupdate
16+
17+
import (
18+
"context"
19+
"errors"
20+
"fmt"
21+
22+
"buf.build/go/app/appcmd"
23+
"buf.build/go/app/appext"
24+
"buf.build/go/standard/xslices"
25+
"github.com/bufbuild/buf/private/buf/bufcli"
26+
"github.com/bufbuild/buf/private/bufpkg/bufparse"
27+
"github.com/bufbuild/buf/private/bufpkg/bufplugin"
28+
"github.com/bufbuild/buf/private/bufpkg/bufpolicy"
29+
"github.com/bufbuild/buf/private/pkg/syserror"
30+
"github.com/spf13/pflag"
31+
)
32+
33+
const (
34+
onlyFlagName = "only"
35+
)
36+
37+
// NewCommand returns a new Command.
38+
func NewCommand(
39+
name string,
40+
builder appext.SubCommandBuilder,
41+
) *appcmd.Command {
42+
flags := newFlags()
43+
return &appcmd.Command{
44+
Use: name + " <directory>",
45+
Short: "Update pinned remote policies in a buf.lock",
46+
Long: `Fetch the latest digests for the specified policy references in buf.yaml.
47+
48+
The first argument is the directory of the local module to update.
49+
Defaults to "." if no argument is specified.`,
50+
Args: appcmd.MaximumNArgs(1),
51+
Run: builder.NewRunFunc(
52+
func(ctx context.Context, container appext.Container) error {
53+
return run(ctx, container, flags)
54+
},
55+
),
56+
BindFlags: flags.Bind,
57+
}
58+
}
59+
60+
type flags struct {
61+
Only []string
62+
}
63+
64+
func newFlags() *flags {
65+
return &flags{}
66+
}
67+
68+
func (f *flags) Bind(flagSet *pflag.FlagSet) {
69+
flagSet.StringSliceVar(
70+
&f.Only,
71+
onlyFlagName,
72+
nil,
73+
"The name of the policy to update. When set, only this policy is updated. May be provided multiple times",
74+
)
75+
// TODO FUTURE: implement
76+
_ = flagSet.MarkHidden(onlyFlagName)
77+
}
78+
79+
func run(
80+
ctx context.Context,
81+
container appext.Container,
82+
flags *flags,
83+
) (retErr error) {
84+
dirPath := "."
85+
if container.NumArgs() > 0 {
86+
dirPath = container.Arg(0)
87+
}
88+
if len(flags.Only) > 0 {
89+
// TODO FUTURE: implement
90+
return syserror.Newf("--%s is not implemented", onlyFlagName)
91+
}
92+
93+
logger := container.Logger()
94+
controller, err := bufcli.NewController(container)
95+
if err != nil {
96+
return err
97+
}
98+
workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath)
99+
if err != nil {
100+
return err
101+
}
102+
configuredRemotePolicyRefs, err := workspaceDepManager.ConfiguredRemotePolicyRefs(ctx)
103+
if err != nil {
104+
return err
105+
}
106+
policyKeyProvider, err := bufcli.NewPolicyKeyProvider(container)
107+
if err != nil {
108+
return err
109+
}
110+
configuredRemotePolicyKeys, err := policyKeyProvider.GetPolicyKeysForPolicyRefs(
111+
ctx,
112+
configuredRemotePolicyRefs,
113+
bufpolicy.DigestTypeO1,
114+
)
115+
if err != nil {
116+
return err
117+
}
118+
configuredPolicyNameToRemotePluginKeys, err := getPolicyKeyPluginKeysForPolicyKeys(
119+
ctx,
120+
container,
121+
configuredRemotePolicyKeys,
122+
)
123+
if err != nil {
124+
return err
125+
}
126+
127+
// Store the existing buf.lock data.
128+
existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx)
129+
if err != nil {
130+
return err
131+
}
132+
if configuredRemotePolicyKeys == nil && existingRemotePolicyKeys == nil {
133+
// No new configured remote plugins were found, and no existing buf.lock deps were found, so there
134+
// is nothing to update, we can return here.
135+
// This ensures we do not create an empty buf.lock when one did not exist in the first
136+
// place and we do not need to go through the entire operation of updating non-existent
137+
// deps and building the image for tamper-proofing.
138+
logger.Warn(fmt.Sprintf("No configured remote policies were found to update in %q.", dirPath))
139+
return nil
140+
}
141+
existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx)
142+
if err != nil {
143+
return err
144+
}
145+
existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx)
146+
if err != nil {
147+
return err
148+
}
149+
existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx)
150+
if err != nil {
151+
return err
152+
}
153+
154+
// We're about to edit the buf.lock file on disk. If we have a subsequent error,
155+
// attempt to revert the buf.lock file.
156+
//
157+
// TODO FUTURE: We should be able to update the buf.lock file in an in-memory bucket, then do the rebuild,
158+
// and if the rebuild is successful, then actually write to disk. It shouldn't even be that much work - just
159+
// overlay the new buf.lock file in a union bucket.
160+
defer func() {
161+
if retErr != nil {
162+
retErr = errors.Join(retErr, workspaceDepManager.UpdateBufLockFile(
163+
ctx, existingDepModuleKeys, existingRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys,
164+
))
165+
}
166+
}()
167+
// Edit the buf.lock file with the updated remote plugins.
168+
if err := workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, existingRemotePluginKeys, configuredRemotePolicyKeys, configuredPolicyNameToRemotePluginKeys); err != nil {
169+
return err
170+
}
171+
return nil
172+
}
173+
174+
func getPolicyKeyPluginKeysForPolicyKeys(
175+
ctx context.Context,
176+
container appext.Container,
177+
policyKeys []bufpolicy.PolicyKey,
178+
) (map[string][]bufplugin.PluginKey, error) {
179+
policyDataProvider, err := bufcli.NewPolicyDataProvider(container)
180+
if err != nil {
181+
return nil, err
182+
}
183+
pluginKeyProvider, err := bufcli.NewPluginKeyProvider(container)
184+
if err != nil {
185+
return nil, err
186+
}
187+
policyDatas, err := policyDataProvider.GetPolicyDatasForPolicyKeys(ctx, policyKeys)
188+
if err != nil {
189+
return nil, err
190+
}
191+
policyNameToRemotePluginKeys := make(map[string][]bufplugin.PluginKey)
192+
for _, policyData := range policyDatas {
193+
policyConfig, err := policyData.Config()
194+
if err != nil {
195+
return nil, err
196+
}
197+
pluginConfigs := policyConfig.PluginConfigs()
198+
pluginRefs, err := xslices.MapError(pluginConfigs, func(pluginConfig bufpolicy.PluginConfig) (bufparse.Ref, error) {
199+
pluginRef := pluginConfig.Ref()
200+
if pluginRef == nil {
201+
return nil, fmt.Errorf("plugin config %q does not have a valid ref", pluginConfig.Name())
202+
}
203+
return pluginRef, nil
204+
})
205+
if err != nil {
206+
return nil, fmt.Errorf("failed to get plugin refs for policy %q: %w", policyData.PolicyKey(), err)
207+
}
208+
remotePluginKeys, err := pluginKeyProvider.GetPluginKeysForPluginRefs(
209+
ctx,
210+
pluginRefs,
211+
bufplugin.DigestTypeP1,
212+
)
213+
if err != nil {
214+
return nil, err
215+
}
216+
policyName := policyData.PolicyKey().FullName().String()
217+
if len(remotePluginKeys) > 0 {
218+
policyNameToRemotePluginKeys[policyName] = remotePluginKeys
219+
}
220+
}
221+
return policyNameToRemotePluginKeys, nil
222+
}

private/buf/cmd/buf/command/policy/policyupdate/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)