Skip to content

Commit 6307ea2

Browse files
bbezakAlex-Welsh
authored andcommitted
send empty X-Registry-Auth for anonymous pushes
Since Docker 28.3.3 the daemon rejects a push that carries no `X-Registry-Auth` header [1]. The SDK already sets this header when it finds credentials, so the breakage happens only on anonymous pushes. During `PushTask.push_image()` we now check whether the SDK can resolve credentials for the target registry; if it cannot, we inject `auth_config={}`, causing the SDK to send the minimal "{}" header that satisfies the daemon while leaving authenticated pushes unchanged. Drop this addition when [2] is fixed. [1] moby/moby#50371 [2] docker/docker-py#3348 Closes-Bug: #2119619 Change-Id: I7a2f3fce223afd74741b40bf62836b325fca5b19 Signed-off-by: Bartosz Bezak <[email protected]> (cherry picked from commit ddac7ca)
1 parent 196509d commit 6307ea2

File tree

2 files changed

+25
-5
lines changed

2 files changed

+25
-5
lines changed

kolla/image/tasks.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ def run(self):
119119

120120
def push_image(self, image):
121121
kwargs = dict(stream=True, decode=True)
122+
# NOTE(bbezak): Docker ≥ 28.3.3 rejects a push with no
123+
# X-Registry-Auth header (moby/moby#50371, docker-py#3348).
124+
# If the SDK cannot find creds for this registry, we inject
125+
# an empty {} so the daemon still accepts the request.
126+
# TODO(bbezak): Remove fallback once docker-py handles empty auth
127+
if self.conf.engine == engine.Engine.DOCKER.value:
128+
from docker.auth import resolve_authconfig
129+
if not resolve_authconfig(self.engine_client.api._auth_configs,
130+
registry=self.conf.registry):
131+
kwargs.setdefault("auth_config", {})
122132

123133
for response in self.engine_client.images.push(image.canonical_name,
124134
**kwargs):

kolla/tests/test_build.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,26 @@ def setUp(self):
8181
@mock.patch(engine_client)
8282
def test_push_image(self, mock_client):
8383
self.engine_client = mock_client
84+
mock_client().api._auth_configs = {}
8485
pusher = tasks.PushTask(self.conf, self.image)
8586
pusher.run()
8687
mock_client().images.push.assert_called_once_with(
87-
self.image.canonical_name, decode=True, stream=True)
88+
self.image.canonical_name,
89+
decode=True, stream=True, auth_config={})
8890
self.assertTrue(pusher.success)
8991

9092
@mock.patch.dict(os.environ, clear=True)
9193
@mock.patch(engine_client)
9294
def test_push_image_failure(self, mock_client):
9395
"""failure on connecting Docker API"""
9496
self.engine_client = mock_client
97+
mock_client().api._auth_configs = {}
9598
mock_client().images.push.side_effect = Exception
9699
pusher = tasks.PushTask(self.conf, self.image)
97100
pusher.run()
98101
mock_client().images.push.assert_called_once_with(
99-
self.image.canonical_name, decode=True, stream=True)
102+
self.image.canonical_name,
103+
decode=True, stream=True, auth_config={})
100104
self.assertFalse(pusher.success)
101105
self.assertEqual(utils.Status.PUSH_ERROR, self.image.status)
102106

@@ -105,11 +109,13 @@ def test_push_image_failure(self, mock_client):
105109
def test_push_image_failure_retry(self, mock_client):
106110
"""failure on connecting Docker API, success on retry"""
107111
self.engine_client = mock_client
112+
mock_client().api._auth_configs = {}
108113
mock_client().images.push.side_effect = [Exception, []]
109114
pusher = tasks.PushTask(self.conf, self.image)
110115
pusher.run()
111116
mock_client().images.push.assert_called_once_with(
112-
self.image.canonical_name, decode=True, stream=True)
117+
self.image.canonical_name,
118+
decode=True, stream=True, auth_config={})
113119
self.assertFalse(pusher.success)
114120
self.assertEqual(utils.Status.PUSH_ERROR, self.image.status)
115121

@@ -125,12 +131,14 @@ def test_push_image_failure_retry(self, mock_client):
125131
def test_push_image_failure_error(self, mock_client):
126132
"""Docker connected, failure to push"""
127133
self.engine_client = mock_client
134+
mock_client().api._auth_configs = {}
128135
mock_client().images.push.return_value = [{'errorDetail': {'message':
129136
'mock push fail'}}]
130137
pusher = tasks.PushTask(self.conf, self.image)
131138
pusher.run()
132139
mock_client().images.push.assert_called_once_with(
133-
self.image.canonical_name, decode=True, stream=True)
140+
self.image.canonical_name,
141+
decode=True, stream=True, auth_config={})
134142
self.assertFalse(pusher.success)
135143
self.assertEqual(utils.Status.PUSH_ERROR, self.image.status)
136144

@@ -139,12 +147,14 @@ def test_push_image_failure_error(self, mock_client):
139147
def test_push_image_failure_error_retry(self, mock_client):
140148
"""Docker connected, failure to push, success on retry"""
141149
self.engine_client = mock_client
150+
mock_client().api._auth_configs = {}
142151
mock_client().images.push.return_value = [{'errorDetail': {'message':
143152
'mock push fail'}}]
144153
pusher = tasks.PushTask(self.conf, self.image)
145154
pusher.run()
146155
mock_client().images.push.assert_called_once_with(
147-
self.image.canonical_name, decode=True, stream=True)
156+
self.image.canonical_name,
157+
decode=True, stream=True, auth_config={})
148158
self.assertFalse(pusher.success)
149159
self.assertEqual(utils.Status.PUSH_ERROR, self.image.status)
150160

0 commit comments

Comments
 (0)