Skip to content

Commit a067c8c

Browse files
Mark AllenCopilot
authored andcommitted
Suppress Docker digest-only updates when tag version is unchanged
When a Dockerfile pins both a tag and a digest (e.g., FROM golang:1.26.3@sha256:...), Dependabot would propose PRs that only update the digest when the same tag was re-pushed on the registry, even though the tag version hadn't changed. This adds a new experiment flag docker_digest_only_update_suppression that, when enabled, treats the digest as up-to-date if the latest resolved tag name matches the current tag name. This prevents noisy digest-only PRs while still updating the digest whenever the tag version actually advances. Fixes #15081 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e15f8ca commit a067c8c

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

docker/lib/dependabot/docker/update_checker.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ def digest_up_to_date?
213213
expected_digest =
214214
if source_tag
215215
latest_tag = latest_tag_from(source_tag)
216+
217+
# When digest-only updates are suppressed and the tag hasn't changed,
218+
# treat the digest as up-to-date to avoid proposing a PR that only
219+
# bumps the digest without a corresponding version change.
220+
if Dependabot::Experiments.enabled?(:docker_digest_only_update_suppression) &&
221+
latest_tag.name == source_tag
222+
next true
223+
end
224+
216225
digest_of(latest_tag.name)
217226
else
218227
updated_digest

docker/spec/dependabot/docker/update_checker_spec.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ def stub_tag_with_no_digest(tag)
8585
end
8686

8787
it { is_expected.to be_falsy }
88+
89+
context "when docker_digest_only_update_suppression experiment is enabled" do
90+
before do
91+
allow(Dependabot::Experiments).to receive(:enabled?)
92+
.with(:docker_digest_only_update_suppression).and_return(true)
93+
allow(Dependabot::Experiments).to receive(:enabled?)
94+
.with(:docker_created_timestamp_validation).and_return(false)
95+
allow(Dependabot::Experiments).to receive(:enabled?)
96+
.with(:docker_pin_digests).and_return(false)
97+
end
98+
99+
it { is_expected.to be_truthy }
100+
end
88101
end
89102
end
90103

@@ -3225,6 +3238,111 @@ def stub_tag_with_no_digest(tag)
32253238
end
32263239
end
32273240

3241+
describe "#digest_up_to_date? with docker_digest_only_update_suppression experiment" do
3242+
subject(:digest_up_to_date?) { checker.send(:digest_up_to_date?) }
3243+
3244+
let(:headers_response) do
3245+
fixture("docker", "registry_manifest_headers", "generic.json")
3246+
end
3247+
3248+
context "when experiment is enabled" do
3249+
before do
3250+
allow(Dependabot::Experiments).to receive(:enabled?)
3251+
.with(:docker_digest_only_update_suppression).and_return(true)
3252+
allow(Dependabot::Experiments).to receive(:enabled?)
3253+
.with(:docker_created_timestamp_validation).and_return(false)
3254+
allow(Dependabot::Experiments).to receive(:enabled?)
3255+
.with(:docker_pin_digests).and_return(false)
3256+
end
3257+
3258+
context "when the tag has not changed but the digest has" do
3259+
let(:version) { "17.10" }
3260+
let(:source) do
3261+
{
3262+
tag: "17.10",
3263+
digest: "old_digest_that_differs_from_registry"
3264+
}
3265+
end
3266+
3267+
before do
3268+
stub_request(:head, repo_url + "manifests/17.10")
3269+
.and_return(status: 200, headers: JSON.parse(headers_response))
3270+
end
3271+
3272+
it "treats the digest as up-to-date (suppresses digest-only update)" do
3273+
expect(digest_up_to_date?).to be true
3274+
end
3275+
end
3276+
3277+
context "when the tag has changed and the digest differs" do
3278+
let(:version) { "17.04" }
3279+
let(:source) do
3280+
{
3281+
tag: "17.04",
3282+
digest: "old_digest"
3283+
}
3284+
end
3285+
3286+
before do
3287+
stub_request(:head, repo_url + "manifests/17.10")
3288+
.and_return(status: 200, headers: JSON.parse(headers_response))
3289+
end
3290+
3291+
it "reports the digest as out of date" do
3292+
expect(digest_up_to_date?).to be false
3293+
end
3294+
end
3295+
3296+
context "when only a digest is present (no tag)" do
3297+
let(:version) { "latest" }
3298+
let(:source) do
3299+
{
3300+
digest: "old_digest"
3301+
}
3302+
end
3303+
3304+
before do
3305+
stub_request(:head, repo_url + "manifests/latest")
3306+
.and_return(status: 200, headers: JSON.parse(headers_response))
3307+
end
3308+
3309+
it "still detects digest changes (suppression only applies to tagged images)" do
3310+
expect(digest_up_to_date?).to be false
3311+
end
3312+
end
3313+
end
3314+
3315+
context "when experiment is disabled" do
3316+
before do
3317+
allow(Dependabot::Experiments).to receive(:enabled?)
3318+
.with(:docker_digest_only_update_suppression).and_return(false)
3319+
allow(Dependabot::Experiments).to receive(:enabled?)
3320+
.with(:docker_created_timestamp_validation).and_return(false)
3321+
allow(Dependabot::Experiments).to receive(:enabled?)
3322+
.with(:docker_pin_digests).and_return(false)
3323+
end
3324+
3325+
context "when the tag has not changed but the digest has" do
3326+
let(:version) { "17.10" }
3327+
let(:source) do
3328+
{
3329+
tag: "17.10",
3330+
digest: "old_digest_that_differs_from_registry"
3331+
}
3332+
end
3333+
3334+
before do
3335+
stub_request(:head, repo_url + "manifests/17.10")
3336+
.and_return(status: 200, headers: JSON.parse(headers_response))
3337+
end
3338+
3339+
it "reports the digest as out of date (existing behavior)" do
3340+
expect(digest_up_to_date?).to be false
3341+
end
3342+
end
3343+
end
3344+
end
3345+
32283346
private
32293347

32303348
def stub_same_sha_for(*tags)

0 commit comments

Comments
 (0)