Browse code

LCOW: Builder plumbing

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2017/05/18 09:08:01
Showing 13 changed files
... ...
@@ -28,4 +28,5 @@ type GetImageAndLayerOptions struct {
28 28
 	ForcePull  bool
29 29
 	AuthConfig map[string]types.AuthConfig
30 30
 	Output     io.Writer
31
+	Platform   string
31 32
 }
... ...
@@ -178,6 +178,10 @@ type ImageBuildOptions struct {
178 178
 	SecurityOpt []string
179 179
 	ExtraHosts  []string // List of extra hosts
180 180
 	Target      string
181
+
182
+	// TODO @jhowardmsft LCOW Support: This will require extending to include
183
+	// `Platform string`, but is ommited for now as it's hard-coded temporarily
184
+	// to avoid API changes.
181 185
 }
182 186
 
183 187
 // ImageBuildResponse holds information
... ...
@@ -16,6 +16,7 @@ type ContainerCreateConfig struct {
16 16
 	HostConfig       *container.HostConfig
17 17
 	NetworkingConfig *network.NetworkingConfig
18 18
 	AdjustCPUShares  bool
19
+	Platform         string
19 20
 }
20 21
 
21 22
 // ContainerRmConfig holds arguments for the container remove
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"fmt"
6 6
 	"io"
7 7
 	"io/ioutil"
8
+	"runtime"
8 9
 	"strings"
9 10
 
10 11
 	"github.com/Sirupsen/logrus"
... ...
@@ -20,6 +21,7 @@ import (
20 20
 	"github.com/docker/docker/pkg/idtools"
21 21
 	"github.com/docker/docker/pkg/streamformatter"
22 22
 	"github.com/docker/docker/pkg/stringid"
23
+	"github.com/docker/docker/pkg/system"
23 24
 	"github.com/pkg/errors"
24 25
 	"golang.org/x/net/context"
25 26
 	"golang.org/x/sync/syncmap"
... ...
@@ -73,13 +75,24 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
73 73
 		}()
74 74
 	}
75 75
 
76
+	// TODO @jhowardmsft LCOW support - this will require rework to allow both linux and Windows simultaneously.
77
+	// This is an interim solution to hardcode to linux if LCOW is turned on.
78
+	if dockerfile.Platform == "" {
79
+		dockerfile.Platform = runtime.GOOS
80
+		if dockerfile.Platform == "windows" && system.LCOWSupported() {
81
+			dockerfile.Platform = "linux"
82
+		}
83
+	}
84
+
76 85
 	builderOptions := builderOptions{
77 86
 		Options:        config.Options,
78 87
 		ProgressWriter: config.ProgressWriter,
79 88
 		Backend:        bm.backend,
80 89
 		PathCache:      bm.pathCache,
81 90
 		Archiver:       bm.archiver,
91
+		Platform:       dockerfile.Platform,
82 92
 	}
93
+
83 94
 	return newBuilder(ctx, builderOptions).build(source, dockerfile)
84 95
 }
85 96
 
... ...
@@ -90,6 +103,7 @@ type builderOptions struct {
90 90
 	ProgressWriter backend.ProgressWriter
91 91
 	PathCache      pathCache
92 92
 	Archiver       *archive.Archiver
93
+	Platform       string
93 94
 }
94 95
 
95 96
 // Builder is a Dockerfile builder
... ...
@@ -113,14 +127,32 @@ type Builder struct {
113 113
 	pathCache        pathCache
114 114
 	containerManager *containerManager
115 115
 	imageProber      ImageProber
116
+
117
+	// TODO @jhowardmft LCOW Support. This will be moved to options at a later
118
+	// stage, however that cannot be done now as it affects the public API
119
+	// if it were.
120
+	platform string
116 121
 }
117 122
 
118 123
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
124
+// TODO @jhowardmsft LCOW support: Eventually platform can be moved into the builder
125
+// options, however, that would be an API change as it shares types.ImageBuildOptions.
119 126
 func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
120 127
 	config := options.Options
121 128
 	if config == nil {
122 129
 		config = new(types.ImageBuildOptions)
123 130
 	}
131
+
132
+	// @jhowardmsft LCOW Support. For the time being, this is interim. Eventually
133
+	// will be moved to types.ImageBuildOptions, but it can't for now as that would
134
+	// be an API change.
135
+	if options.Platform == "" {
136
+		options.Platform = runtime.GOOS
137
+	}
138
+	if options.Platform == "windows" && system.LCOWSupported() {
139
+		options.Platform = "linux"
140
+	}
141
+
124 142
 	b := &Builder{
125 143
 		clientCtx:        clientCtx,
126 144
 		options:          config,
... ...
@@ -136,7 +168,9 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
136 136
 		pathCache:        options.PathCache,
137 137
 		imageProber:      newImageProber(options.Backend, config.CacheFrom, config.NoCache),
138 138
 		containerManager: newContainerManager(options.Backend),
139
+		platform:         options.Platform,
139 140
 	}
141
+
140 142
 	return b
141 143
 }
142 144
 
... ...
@@ -267,6 +301,17 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
267 267
 		return nil, err
268 268
 	}
269 269
 
270
+	// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
271
+	// Also explicitly set the platform. Ultimately this will be in the builder
272
+	// options, but we can't do that yet as it would change the API.
273
+	if dockerfile.Platform == "" {
274
+		dockerfile.Platform = runtime.GOOS
275
+	}
276
+	if dockerfile.Platform == "windows" && system.LCOWSupported() {
277
+		dockerfile.Platform = "linux"
278
+	}
279
+	b.platform = dockerfile.Platform
280
+
270 281
 	// ensure that the commands are valid
271 282
 	for _, n := range dockerfile.AST.Children {
272 283
 		if !validCommitCommands[n.Value] {
... ...
@@ -28,10 +28,11 @@ func newContainerManager(docker builder.ExecBackend) *containerManager {
28 28
 }
29 29
 
30 30
 // Create a container
31
-func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig) (container.ContainerCreateCreatedBody, error) {
31
+func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig, platform string) (container.ContainerCreateCreatedBody, error) {
32 32
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
33 33
 		Config:     runConfig,
34 34
 		HostConfig: hostConfig,
35
+		Platform:   platform,
35 36
 	})
36 37
 	if err != nil {
37 38
 		return container, err
... ...
@@ -26,6 +26,7 @@ import (
26 26
 	"github.com/docker/docker/image"
27 27
 	"github.com/docker/docker/pkg/jsonmessage"
28 28
 	"github.com/docker/docker/pkg/signal"
29
+	"github.com/docker/docker/pkg/system"
29 30
 	"github.com/docker/go-connections/nat"
30 31
 	"github.com/pkg/errors"
31 32
 )
... ...
@@ -270,10 +271,12 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err
270 270
 		name = stage.ImageID()
271 271
 	}
272 272
 
273
-	// Windows cannot support a container with no base image.
273
+	// Windows cannot support a container with no base image unless it is LCOW.
274 274
 	if name == api.NoBaseImageSpecifier {
275 275
 		if runtime.GOOS == "windows" {
276
-			return nil, errors.New("Windows does not support FROM scratch")
276
+			if b.platform == "windows" || (b.platform != "windows" && !system.LCOWSupported()) {
277
+				return nil, errors.New("Windows does not support FROM scratch")
278
+			}
277 279
 		}
278 280
 		return scratchImage, nil
279 281
 	}
... ...
@@ -194,7 +194,7 @@ func TestFromScratch(t *testing.T) {
194 194
 	req := defaultDispatchReq(b, "scratch")
195 195
 	err := from(req)
196 196
 
197
-	if runtime.GOOS == "windows" {
197
+	if runtime.GOOS == "windows" && !system.LCOWSupported() {
198 198
 		assert.EqualError(t, err, "Windows does not support FROM scratch")
199 199
 		return
200 200
 	}
... ...
@@ -202,7 +202,12 @@ func TestFromScratch(t *testing.T) {
202 202
 	require.NoError(t, err)
203 203
 	assert.True(t, req.state.hasFromImage())
204 204
 	assert.Equal(t, "", req.state.imageID)
205
-	assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env)
205
+	// Windows does not set the default path. TODO @jhowardmsft LCOW support. This will need revisiting as we get further into the implementation
206
+	expected := "PATH=" + system.DefaultPathEnv
207
+	if runtime.GOOS == "windows" {
208
+		expected = ""
209
+	}
210
+	assert.Equal(t, []string{expected}, req.state.runConfig.Env)
206 211
 }
207 212
 
208 213
 func TestFromWithArg(t *testing.T) {
... ...
@@ -97,12 +97,15 @@ type imageSources struct {
97 97
 	cache     pathCache // TODO: remove
98 98
 }
99 99
 
100
+// TODO @jhowardmsft LCOW Support: Eventually, platform can be moved to options.Options.Platform,
101
+// and removed from builderOptions, but that can't be done yet as it would affect the API.
100 102
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
101 103
 	getAndMount := func(idOrRef string) (builder.Image, builder.ReleaseableLayer, error) {
102 104
 		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
103 105
 			ForcePull:  options.Options.PullParent,
104 106
 			AuthConfig: options.Options.AuthConfigs,
105 107
 			Output:     options.ProgressWriter.Output,
108
+			Platform:   options.Platform,
106 109
 		})
107 110
 	}
108 111
 
... ...
@@ -256,13 +256,13 @@ func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *contai
256 256
 	}
257 257
 	// Set a log config to override any default value set on the daemon
258 258
 	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
259
-	container, err := b.containerManager.Create(runConfig, hostConfig)
259
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
260 260
 	return container.ID, err
261 261
 }
262 262
 
263 263
 func (b *Builder) create(runConfig *container.Config) (string, error) {
264 264
 	hostConfig := hostConfigFromOptions(b.options)
265
-	container, err := b.containerManager.Create(runConfig, hostConfig)
265
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
266 266
 	if err != nil {
267 267
 		return "", err
268 268
 	}
... ...
@@ -7,11 +7,13 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"regexp"
10
+	"runtime"
10 11
 	"strconv"
11 12
 	"strings"
12 13
 	"unicode"
13 14
 
14 15
 	"github.com/docker/docker/builder/dockerfile/command"
16
+	"github.com/docker/docker/pkg/system"
15 17
 	"github.com/pkg/errors"
16 18
 )
17 19
 
... ...
@@ -79,22 +81,28 @@ func (node *Node) AddChild(child *Node, startLine, endLine int) {
79 79
 }
80 80
 
81 81
 var (
82
-	dispatch           map[string]func(string, *Directive) (*Node, map[string]bool, error)
83
-	tokenWhitespace    = regexp.MustCompile(`[\t\v\f\r ]+`)
84
-	tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
85
-	tokenComment       = regexp.MustCompile(`^#.*$`)
82
+	dispatch             map[string]func(string, *Directive) (*Node, map[string]bool, error)
83
+	tokenWhitespace      = regexp.MustCompile(`[\t\v\f\r ]+`)
84
+	tokenEscapeCommand   = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
85
+	tokenPlatformCommand = regexp.MustCompile(`^#[ \t]*platform[ \t]*=[ \t]*(?P<platform>.*)$`)
86
+	tokenComment         = regexp.MustCompile(`^#.*$`)
86 87
 )
87 88
 
88 89
 // DefaultEscapeToken is the default escape token
89 90
 const DefaultEscapeToken = '\\'
90 91
 
92
+// DefaultPlatformToken is the platform assumed for the build if not explicitly provided
93
+var DefaultPlatformToken = runtime.GOOS
94
+
91 95
 // Directive is the structure used during a build run to hold the state of
92 96
 // parsing directives.
93 97
 type Directive struct {
94 98
 	escapeToken           rune           // Current escape token
99
+	platformToken         string         // Current platform token
95 100
 	lineContinuationRegex *regexp.Regexp // Current line continuation regex
96 101
 	processingComplete    bool           // Whether we are done looking for directives
97 102
 	escapeSeen            bool           // Whether the escape directive has been seen
103
+	platformSeen          bool           // Whether the platform directive has been seen
98 104
 }
99 105
 
100 106
 // setEscapeToken sets the default token for escaping characters in a Dockerfile.
... ...
@@ -107,6 +115,22 @@ func (d *Directive) setEscapeToken(s string) error {
107 107
 	return nil
108 108
 }
109 109
 
110
+// setPlatformToken sets the default platform for pulling images in a Dockerfile.
111
+func (d *Directive) setPlatformToken(s string) error {
112
+	s = strings.ToLower(s)
113
+	valid := []string{runtime.GOOS}
114
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
115
+		valid = append(valid, "linux")
116
+	}
117
+	for _, item := range valid {
118
+		if s == item {
119
+			d.platformToken = s
120
+			return nil
121
+		}
122
+	}
123
+	return fmt.Errorf("invalid PLATFORM '%s'. Must be one of %v", s, valid)
124
+}
125
+
110 126
 // possibleParserDirective looks for one or more parser directives '# escapeToken=<char>' and
111 127
 // '# platform=<string>'. Parser directives must precede any builder instruction
112 128
 // or other comments, and cannot be repeated.
... ...
@@ -128,6 +152,23 @@ func (d *Directive) possibleParserDirective(line string) error {
128 128
 		}
129 129
 	}
130 130
 
131
+	// TODO @jhowardmsft LCOW Support: Eventually this check can be removed,
132
+	// but only recognise a platform token if running in LCOW mode.
133
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
134
+		tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line))
135
+		if len(tpcMatch) != 0 {
136
+			for i, n := range tokenPlatformCommand.SubexpNames() {
137
+				if n == "platform" {
138
+					if d.platformSeen == true {
139
+						return errors.New("only one platform parser directive can be used")
140
+					}
141
+					d.platformSeen = true
142
+					return d.setPlatformToken(tpcMatch[i])
143
+				}
144
+			}
145
+		}
146
+	}
147
+
131 148
 	d.processingComplete = true
132 149
 	return nil
133 150
 }
... ...
@@ -136,6 +177,7 @@ func (d *Directive) possibleParserDirective(line string) error {
136 136
 func NewDefaultDirective() *Directive {
137 137
 	directive := Directive{}
138 138
 	directive.setEscapeToken(string(DefaultEscapeToken))
139
+	directive.setPlatformToken(runtime.GOOS)
139 140
 	return &directive
140 141
 }
141 142
 
... ...
@@ -200,6 +242,7 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
200 200
 type Result struct {
201 201
 	AST         *Node
202 202
 	EscapeToken rune
203
+	Platform    string
203 204
 }
204 205
 
205 206
 // Parse reads lines from a Reader, parses the lines into an AST and returns
... ...
@@ -252,7 +295,7 @@ func Parse(rwc io.Reader) (*Result, error) {
252 252
 		}
253 253
 		root.AddChild(child, startLine, currentLine)
254 254
 	}
255
-	return &Result{AST: root, EscapeToken: d.escapeToken}, nil
255
+	return &Result{AST: root, EscapeToken: d.escapeToken, Platform: d.platformToken}, nil
256 256
 }
257 257
 
258 258
 func trimComments(src []byte) []byte {
... ...
@@ -2,7 +2,6 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"io"
5
-	"runtime"
6 5
 
7 6
 	"github.com/Sirupsen/logrus"
8 7
 	"github.com/docker/distribution/reference"
... ...
@@ -110,7 +109,7 @@ func newReleasableLayerForImage(img *image.Image, layerStore layer.Store) (build
110 110
 }
111 111
 
112 112
 // TODO: could this use the regular daemon PullImage ?
113
-func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (*image.Image, error) {
113
+func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, platform string) (*image.Image, error) {
114 114
 	ref, err := reference.ParseNormalizedNamed(name)
115 115
 	if err != nil {
116 116
 		return nil, err
... ...
@@ -129,12 +128,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
129 129
 		pullRegistryAuth = &resolvedConfig
130 130
 	}
131 131
 
132
-	// TODO @jhowardmsft LCOW Support: For now, use the runtime operating system of the host.
133
-	// When it gets to the builder part, this will need revisiting. There would have to be
134
-	// some indication from the user either through CLI flag to build, or through an explicit
135
-	// mechanism in a dockerfile such as a parser directive extension or an addition to
136
-	// the FROM statement syntax.
137
-	if err := daemon.pullImageWithReference(ctx, ref, runtime.GOOS, nil, pullRegistryAuth, output); err != nil {
132
+	if err := daemon.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
138 133
 		return nil, err
139 134
 	}
140 135
 	return daemon.GetImage(name)
... ...
@@ -153,18 +147,16 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st
153 153
 		image, _ := daemon.GetImage(refOrID)
154 154
 		// TODO: shouldn't we error out if error is different from "not found" ?
155 155
 		if image != nil {
156
-			// TODO LCOW @jhowardmsft. For now using runtime.GOOS for this, will need enhancing for platform when porting the builder
157
-			layer, err := newReleasableLayerForImage(image, daemon.stores[runtime.GOOS].layerStore)
156
+			layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
158 157
 			return image, layer, err
159 158
 		}
160 159
 	}
161 160
 
162
-	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output)
161
+	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
163 162
 	if err != nil {
164 163
 		return nil, nil, err
165 164
 	}
166
-	// TODO LCOW @jhowardmsft. For now using runtime.GOOS for this, will need enhancing for platform when porting the builder
167
-	layer, err := newReleasableLayerForImage(image, daemon.stores[runtime.GOOS].layerStore)
165
+	layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
168 166
 	return image, layer, err
169 167
 }
170 168
 
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"fmt"
5 5
 	"os"
6 6
 	"path/filepath"
7
-	"runtime"
8 7
 	"time"
9 8
 
10 9
 	"github.com/docker/docker/api/errors"
... ...
@@ -112,7 +111,7 @@ func (daemon *Daemon) Register(c *container.Container) {
112 112
 	daemon.idIndex.Add(c.ID)
113 113
 }
114 114
 
115
-func (daemon *Daemon) newContainer(name string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
115
+func (daemon *Daemon) newContainer(name string, platform string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
116 116
 	var (
117 117
 		id             string
118 118
 		err            error
... ...
@@ -145,10 +144,8 @@ func (daemon *Daemon) newContainer(name string, config *containertypes.Config, h
145 145
 	base.ImageID = imgID
146 146
 	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
147 147
 	base.Name = name
148
-	// TODO @jhowardmsft LCOW - Get it from the platform of the container. For now, assume it is the OS of the host
149
-	base.Driver = daemon.GraphDriverName(runtime.GOOS)
150
-	// TODO @jhowardmsft LCOW - Similarly on this field. To solve this it will need a CLI/REST change in a subsequent PR during LCOW development
151
-	base.Platform = runtime.GOOS
148
+	base.Driver = daemon.GraphDriverName(platform)
149
+	base.Platform = platform
152 150
 	return base, err
153 151
 }
154 152
 
... ...
@@ -19,6 +19,7 @@ import (
19 19
 	"github.com/docker/docker/layer"
20 20
 	"github.com/docker/docker/pkg/idtools"
21 21
 	"github.com/docker/docker/pkg/stringid"
22
+	"github.com/docker/docker/pkg/system"
22 23
 	"github.com/docker/docker/runconfig"
23 24
 	"github.com/opencontainers/selinux/go-selinux/label"
24 25
 )
... ...
@@ -75,6 +76,16 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
75 75
 		err       error
76 76
 	)
77 77
 
78
+	// TODO: @jhowardmsft LCOW support - at a later point, can remove the hard-coding
79
+	// to force the platform to be linux.
80
+	// Default the platform if not supplied
81
+	if params.Platform == "" {
82
+		params.Platform = runtime.GOOS
83
+	}
84
+	if params.Platform == "windows" && system.LCOWSupported() {
85
+		params.Platform = "linux"
86
+	}
87
+
78 88
 	if params.Config.Image != "" {
79 89
 		img, err = daemon.GetImage(params.Config.Image)
80 90
 		if err != nil {
... ...
@@ -82,9 +93,23 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
82 82
 		}
83 83
 
84 84
 		if runtime.GOOS == "solaris" && img.OS != "solaris " {
85
-			return nil, errors.New("Platform on which parent image was created is not Solaris")
85
+			return nil, errors.New("platform on which parent image was created is not Solaris")
86 86
 		}
87 87
 		imgID = img.ID()
88
+
89
+		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
90
+			return nil, errors.New("platform on which parent image was created is not Windows")
91
+		}
92
+	}
93
+
94
+	// Make sure the platform requested matches the image
95
+	if img != nil {
96
+		if params.Platform != img.Platform() {
97
+			// Ignore this in LCOW mode. @jhowardmsft TODO - This will need revisiting later.
98
+			if !(runtime.GOOS == "windows" && system.LCOWSupported()) {
99
+				return nil, fmt.Errorf("cannot create a %s container from a %s image", params.Platform, img.Platform())
100
+			}
101
+		}
88 102
 	}
89 103
 
90 104
 	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
... ...
@@ -95,7 +120,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
95 95
 		return nil, err
96 96
 	}
97 97
 
98
-	if container, err = daemon.newContainer(params.Name, params.Config, params.HostConfig, imgID, managed); err != nil {
98
+	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
99 99
 		return nil, err
100 100
 	}
101 101
 	defer func() {