Skip to content

Commit fe18caf

Browse files
committed
pkg/compose: use negotiated API version for network setup
Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
1 parent fa9762b commit fe18caf

4 files changed

Lines changed: 67 additions & 11 deletions

File tree

pkg/compose/compose.go

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ type composeService struct {
215215
clock clockwork.Clock
216216
maxConcurrency int
217217
dryRun bool
218+
219+
runtimeVersion runtimeVersionCache
220+
currentAPIVersion runtimeVersionCache
218221
}
219222

220223
// Close releases any connections/resources held by the underlying clients.
@@ -497,16 +500,42 @@ type runtimeVersionCache struct {
497500
err error
498501
}
499502

500-
var runtimeVersion runtimeVersionCache
501-
502503
func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) {
503-
// TODO(thaJeztah): this should use Client.ClientVersion), which has the negotiated version.
504-
runtimeVersion.once.Do(func() {
504+
// RuntimeVersion returns the raw API version reported by the daemon.
505+
// Callers that need the negotiated/effective client API version should use
506+
// CurrentAPIVersion instead.
507+
s.runtimeVersion.once.Do(func() {
505508
version, err := s.apiClient().ServerVersion(ctx, client.ServerVersionOptions{})
506509
if err != nil {
507-
runtimeVersion.err = err
510+
s.runtimeVersion.err = err
511+
return
508512
}
509-
runtimeVersion.val = version.APIVersion
513+
s.runtimeVersion.val = version.APIVersion
510514
})
511-
return runtimeVersion.val, runtimeVersion.err
515+
return s.runtimeVersion.val, s.runtimeVersion.err
516+
}
517+
518+
// CurrentAPIVersion returns the API version currently used by the Docker client.
519+
// Trigger negotiation first so version-gated request shaping matches the version
520+
// that subsequent API calls will actually use.
521+
func (s *composeService) CurrentAPIVersion(ctx context.Context) (string, error) {
522+
s.currentAPIVersion.once.Do(func() {
523+
_, err := s.apiClient().Ping(ctx, client.PingOptions{NegotiateAPIVersion: true})
524+
if err != nil {
525+
s.currentAPIVersion.err = err
526+
return
527+
}
528+
529+
version := s.apiClient().ClientVersion()
530+
if version != "" {
531+
s.currentAPIVersion.val = version
532+
return
533+
}
534+
535+
// Defensive fallback for unexpected client implementations or mocks that
536+
// do not populate ClientVersion after a successful negotiated ping.
537+
s.currentAPIVersion.val, s.currentAPIVersion.err = s.RuntimeVersion(ctx)
538+
})
539+
540+
return s.currentAPIVersion.val, s.currentAPIVersion.err
512541
}

pkg/compose/convergence_test.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,10 @@ func TestCreateMobyContainer(t *testing.T) {
411411
apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
412412
apiClient.EXPECT().ImageInspect(anyCancellableContext(), gomock.Any()).Return(client.ImageInspectResult{}, nil).AnyTimes()
413413

414-
// force `RuntimeVersion` to fetch fresh version
415-
runtimeVersion = runtimeVersionCache{}
416-
apiClient.EXPECT().ServerVersion(gomock.Any(), gomock.Any()).Return(client.ServerVersionResult{
414+
apiClient.EXPECT().Ping(gomock.Any(), client.PingOptions{NegotiateAPIVersion: true}).Return(client.PingResult{
417415
APIVersion: "1.44",
418416
}, nil).AnyTimes()
417+
apiClient.EXPECT().ClientVersion().Return("1.44").AnyTimes()
419418

420419
service := types.ServiceConfig{
421420
Name: "test",
@@ -498,3 +497,27 @@ func TestCreateMobyContainer(t *testing.T) {
498497
assert.DeepEqual(t, want, got, cmpopts.EquateComparable(netip.Addr{}), cmpopts.EquateEmpty())
499498
assert.NilError(t, err)
500499
}
500+
501+
func TestCurrentAPIVersionCachesNegotiation(t *testing.T) {
502+
mockCtrl := gomock.NewController(t)
503+
defer mockCtrl.Finish()
504+
505+
apiClient := mocks.NewMockAPIClient(mockCtrl)
506+
cli := mocks.NewMockCli(mockCtrl)
507+
tested := &composeService{dockerCli: cli}
508+
509+
cli.EXPECT().Client().Return(apiClient).AnyTimes()
510+
511+
apiClient.EXPECT().Ping(gomock.Any(), client.PingOptions{NegotiateAPIVersion: true}).Return(client.PingResult{
512+
APIVersion: "1.44",
513+
}, nil).Times(1)
514+
apiClient.EXPECT().ClientVersion().Return("1.43").Times(1)
515+
516+
version, err := tested.CurrentAPIVersion(t.Context())
517+
assert.NilError(t, err)
518+
assert.Equal(t, version, "1.43")
519+
520+
version, err = tested.CurrentAPIVersion(t.Context())
521+
assert.NilError(t, err)
522+
assert.Equal(t, version, "1.43")
523+
}

pkg/compose/create.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
252252
if err != nil {
253253
return createConfigs{}, err
254254
}
255-
apiVersion, err := s.RuntimeVersion(ctx)
255+
apiVersion, err := s.CurrentAPIVersion(ctx)
256256
if err != nil {
257257
return createConfigs{}, err
258258
}
@@ -897,6 +897,8 @@ func (s *composeService) buildContainerVolumes(
897897
}
898898
}
899899
case mount.TypeImage:
900+
// This is a daemon capability check for mount type=image support, so
901+
// use the raw server version rather than the negotiated request version.
900902
version, err := s.RuntimeVersion(ctx)
901903
if err != nil {
902904
return nil, nil, err

pkg/compose/images.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
5656
containers = allContainers.Items
5757
}
5858

59+
// This is a daemon capability check for the ImageInspect platform field, so
60+
// use the raw server version rather than the negotiated request version.
5961
version, err := s.RuntimeVersion(ctx)
6062
if err != nil {
6163
return nil, err

0 commit comments

Comments
 (0)