Remove runConfig from Builder and dispatchRequest. It is not only on
dispatchState.
Move dispatch state fields from Builder to dispatchState
Move stageName tracking to dispatchRequest.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -1,5 +1,10 @@ |
| 1 | 1 |
package dockerfile |
| 2 | 2 |
|
| 3 |
+import ( |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "github.com/docker/docker/runconfig/opts" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 3 | 8 |
// builtinAllowedBuildArgs is list of built-in allowed build args |
| 4 | 9 |
// these args are considered transparent and are excluded from the image history. |
| 5 | 10 |
// Filtering from history is implemented in dispatchers.go |
| ... | ... |
@@ -96,6 +101,19 @@ func (b *buildArgs) getAllFromMapping(source map[string]*string) map[string]stri |
| 96 | 96 |
return m |
| 97 | 97 |
} |
| 98 | 98 |
|
| 99 |
+// FilterAllowed returns all allowed args without the filtered args |
|
| 100 |
+func (b *buildArgs) FilterAllowed(filter []string) []string {
|
|
| 101 |
+ envs := []string{}
|
|
| 102 |
+ configEnv := opts.ConvertKVStringsToMap(filter) |
|
| 103 |
+ |
|
| 104 |
+ for key, val := range b.GetAllAllowed() {
|
|
| 105 |
+ if _, ok := configEnv[key]; !ok {
|
|
| 106 |
+ envs = append(envs, fmt.Sprintf("%s=%s", key, val))
|
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ return envs |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 99 | 112 |
func (b *buildArgs) getBuildArg(key string, mapping map[string]*string) (string, bool) {
|
| 100 | 113 |
defaultValue, exists := mapping[key] |
| 101 | 114 |
// Return override from options if one is defined |
| ... | ... |
@@ -95,20 +95,12 @@ type Builder struct {
|
| 95 | 95 |
source builder.Source |
| 96 | 96 |
clientCtx context.Context |
| 97 | 97 |
|
| 98 |
- runConfig *container.Config // runconfig for cmd, run, entrypoint etc. |
|
| 99 | 98 |
tmpContainers map[string]struct{}
|
| 100 | 99 |
imageContexts *imageContexts // helper for storing contexts from builds |
| 101 | 100 |
disableCommit bool |
| 102 | 101 |
cacheBusted bool |
| 103 | 102 |
buildArgs *buildArgs |
| 104 | 103 |
imageCache builder.ImageCache |
| 105 |
- |
|
| 106 |
- // TODO: these move to DispatchState |
|
| 107 |
- maintainer string |
|
| 108 |
- cmdSet bool |
|
| 109 |
- noBaseImage bool // A flag to track the use of `scratch` as the base image |
|
| 110 |
- image string // imageID |
|
| 111 |
- from builder.Image |
|
| 112 | 104 |
} |
| 113 | 105 |
|
| 114 | 106 |
// newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options. |
| ... | ... |
@@ -124,7 +116,6 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
|
| 124 | 124 |
Stderr: options.ProgressWriter.StderrFormatter, |
| 125 | 125 |
Output: options.ProgressWriter.Output, |
| 126 | 126 |
docker: options.Backend, |
| 127 |
- runConfig: new(container.Config), |
|
| 128 | 127 |
tmpContainers: map[string]struct{}{},
|
| 129 | 128 |
buildArgs: newBuildArgs(config.BuildArgs), |
| 130 | 129 |
} |
| ... | ... |
@@ -136,7 +127,6 @@ func (b *Builder) resetImageCache() {
|
| 136 | 136 |
if icb, ok := b.docker.(builder.ImageCacheBuilder); ok {
|
| 137 | 137 |
b.imageCache = icb.MakeImageCache(b.options.CacheFrom) |
| 138 | 138 |
} |
| 139 |
- b.noBaseImage = false |
|
| 140 | 139 |
b.cacheBusted = false |
| 141 | 140 |
} |
| 142 | 141 |
|
| ... | ... |
@@ -154,59 +144,61 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil |
| 154 | 154 |
return nil, err |
| 155 | 155 |
} |
| 156 | 156 |
|
| 157 |
- imageID, err := b.dispatchDockerfileWithCancellation(dockerfile) |
|
| 157 |
+ dispatchState, err := b.dispatchDockerfileWithCancellation(dockerfile) |
|
| 158 | 158 |
if err != nil {
|
| 159 | 159 |
return nil, err |
| 160 | 160 |
} |
| 161 | 161 |
|
| 162 |
+ if b.options.Target != "" && !dispatchState.isCurrentStage(b.options.Target) {
|
|
| 163 |
+ return nil, errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
|
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 162 | 166 |
b.warnOnUnusedBuildArgs() |
| 163 | 167 |
|
| 164 |
- if imageID == "" {
|
|
| 168 |
+ if dispatchState.imageID == "" {
|
|
| 165 | 169 |
return nil, errors.New("No image was generated. Is your Dockerfile empty?")
|
| 166 | 170 |
} |
| 167 |
- return &builder.Result{ImageID: imageID, FromImage: b.from}, nil
|
|
| 171 |
+ return &builder.Result{ImageID: dispatchState.imageID, FromImage: dispatchState.baseImage}, nil
|
|
| 168 | 172 |
} |
| 169 | 173 |
|
| 170 |
-func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Result) (string, error) {
|
|
| 174 |
+func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Result) (*dispatchState, error) {
|
|
| 171 | 175 |
shlex := NewShellLex(dockerfile.EscapeToken) |
| 172 |
- |
|
| 176 |
+ state := newDispatchState() |
|
| 173 | 177 |
total := len(dockerfile.AST.Children) |
| 174 |
- var imageID string |
|
| 178 |
+ var err error |
|
| 175 | 179 |
for i, n := range dockerfile.AST.Children {
|
| 176 | 180 |
select {
|
| 177 | 181 |
case <-b.clientCtx.Done(): |
| 178 | 182 |
logrus.Debug("Builder: build cancelled!")
|
| 179 | 183 |
fmt.Fprint(b.Stdout, "Build cancelled") |
| 180 |
- return "", errors.New("Build cancelled")
|
|
| 184 |
+ return nil, errors.New("Build cancelled")
|
|
| 181 | 185 |
default: |
| 182 | 186 |
// Not cancelled yet, keep going... |
| 183 | 187 |
} |
| 184 | 188 |
|
| 185 |
- if command.From == n.Value && b.imageContexts.isCurrentTarget(b.options.Target) {
|
|
| 189 |
+ if n.Value == command.From && state.isCurrentStage(b.options.Target) {
|
|
| 186 | 190 |
break |
| 187 | 191 |
} |
| 188 | 192 |
|
| 189 |
- if err := b.dispatch(i, total, n, shlex); err != nil {
|
|
| 193 |
+ opts := dispatchOptions{
|
|
| 194 |
+ state: state, |
|
| 195 |
+ stepMsg: formatStep(i, total), |
|
| 196 |
+ node: n, |
|
| 197 |
+ shlex: shlex, |
|
| 198 |
+ } |
|
| 199 |
+ if state, err = b.dispatch(opts); err != nil {
|
|
| 190 | 200 |
if b.options.ForceRemove {
|
| 191 | 201 |
b.clearTmp() |
| 192 | 202 |
} |
| 193 |
- return "", err |
|
| 203 |
+ return nil, err |
|
| 194 | 204 |
} |
| 195 | 205 |
|
| 196 |
- // TODO: get this from dispatch |
|
| 197 |
- imageID = b.image |
|
| 198 |
- |
|
| 199 |
- fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(imageID)) |
|
| 206 |
+ fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(state.imageID)) |
|
| 200 | 207 |
if b.options.Remove {
|
| 201 | 208 |
b.clearTmp() |
| 202 | 209 |
} |
| 203 | 210 |
} |
| 204 |
- |
|
| 205 |
- if b.options.Target != "" && !b.imageContexts.isCurrentTarget(b.options.Target) {
|
|
| 206 |
- return "", errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
|
|
| 207 |
- } |
|
| 208 |
- |
|
| 209 |
- return imageID, nil |
|
| 211 |
+ return state, nil |
|
| 210 | 212 |
} |
| 211 | 213 |
|
| 212 | 214 |
func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
|
| ... | ... |
@@ -227,12 +219,6 @@ func (b *Builder) warnOnUnusedBuildArgs() {
|
| 227 | 227 |
} |
| 228 | 228 |
} |
| 229 | 229 |
|
| 230 |
-// hasFromImage returns true if the builder has processed a `FROM <image>` line |
|
| 231 |
-// TODO: move to DispatchState |
|
| 232 |
-func (b *Builder) hasFromImage() bool {
|
|
| 233 |
- return b.image != "" || b.noBaseImage |
|
| 234 |
-} |
|
| 235 |
- |
|
| 236 | 230 |
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile |
| 237 | 231 |
// It will: |
| 238 | 232 |
// - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries. |
| ... | ... |
@@ -249,31 +235,28 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con |
| 249 | 249 |
|
| 250 | 250 |
b := newBuilder(context.Background(), builderOptions{})
|
| 251 | 251 |
|
| 252 |
- result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) |
|
| 252 |
+ dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) |
|
| 253 | 253 |
if err != nil {
|
| 254 | 254 |
return nil, err |
| 255 | 255 |
} |
| 256 | 256 |
|
| 257 | 257 |
// ensure that the commands are valid |
| 258 |
- for _, n := range result.AST.Children {
|
|
| 258 |
+ for _, n := range dockerfile.AST.Children {
|
|
| 259 | 259 |
if !validCommitCommands[n.Value] {
|
| 260 | 260 |
return nil, fmt.Errorf("%s is not a valid change command", n.Value)
|
| 261 | 261 |
} |
| 262 | 262 |
} |
| 263 | 263 |
|
| 264 |
- b.runConfig = config |
|
| 265 | 264 |
b.Stdout = ioutil.Discard |
| 266 | 265 |
b.Stderr = ioutil.Discard |
| 267 | 266 |
b.disableCommit = true |
| 268 | 267 |
|
| 269 |
- if err := checkDispatchDockerfile(result.AST); err != nil {
|
|
| 270 |
- return nil, err |
|
| 271 |
- } |
|
| 272 |
- |
|
| 273 |
- if err := dispatchFromDockerfile(b, result); err != nil {
|
|
| 268 |
+ if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
|
|
| 274 | 269 |
return nil, err |
| 275 | 270 |
} |
| 276 |
- return b.runConfig, nil |
|
| 271 |
+ dispatchState := newDispatchState() |
|
| 272 |
+ dispatchState.runConfig = config |
|
| 273 |
+ return dispatchFromDockerfile(b, dockerfile, dispatchState) |
|
| 277 | 274 |
} |
| 278 | 275 |
|
| 279 | 276 |
func checkDispatchDockerfile(dockerfile *parser.Node) error {
|
| ... | ... |
@@ -285,15 +268,21 @@ func checkDispatchDockerfile(dockerfile *parser.Node) error {
|
| 285 | 285 |
return nil |
| 286 | 286 |
} |
| 287 | 287 |
|
| 288 |
-func dispatchFromDockerfile(b *Builder, result *parser.Result) error {
|
|
| 288 |
+func dispatchFromDockerfile(b *Builder, result *parser.Result, dispatchState *dispatchState) (*container.Config, error) {
|
|
| 289 | 289 |
shlex := NewShellLex(result.EscapeToken) |
| 290 | 290 |
ast := result.AST |
| 291 | 291 |
total := len(ast.Children) |
| 292 | 292 |
|
| 293 | 293 |
for i, n := range ast.Children {
|
| 294 |
- if err := b.dispatch(i, total, n, shlex); err != nil {
|
|
| 295 |
- return err |
|
| 294 |
+ opts := dispatchOptions{
|
|
| 295 |
+ state: dispatchState, |
|
| 296 |
+ stepMsg: formatStep(i, total), |
|
| 297 |
+ node: n, |
|
| 298 |
+ shlex: shlex, |
|
| 299 |
+ } |
|
| 300 |
+ if _, err := b.dispatch(opts); err != nil {
|
|
| 301 |
+ return nil, err |
|
| 296 | 302 |
} |
| 297 | 303 |
} |
| 298 |
- return nil |
|
| 304 |
+ return dispatchState.runConfig, nil |
|
| 299 | 305 |
} |
| ... | ... |
@@ -47,6 +47,7 @@ func env(req dispatchRequest) error {
|
| 47 | 47 |
return err |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 |
+ runConfig := req.state.runConfig |
|
| 50 | 51 |
commitMessage := bytes.NewBufferString("ENV")
|
| 51 | 52 |
|
| 52 | 53 |
for j := 0; j < len(req.args); j += 2 {
|
| ... | ... |
@@ -59,21 +60,21 @@ func env(req dispatchRequest) error {
|
| 59 | 59 |
commitMessage.WriteString(" " + newVar)
|
| 60 | 60 |
|
| 61 | 61 |
gotOne := false |
| 62 |
- for i, envVar := range req.runConfig.Env {
|
|
| 62 |
+ for i, envVar := range runConfig.Env {
|
|
| 63 | 63 |
envParts := strings.SplitN(envVar, "=", 2) |
| 64 | 64 |
compareFrom := envParts[0] |
| 65 | 65 |
if equalEnvKeys(compareFrom, name) {
|
| 66 |
- req.runConfig.Env[i] = newVar |
|
| 66 |
+ runConfig.Env[i] = newVar |
|
| 67 | 67 |
gotOne = true |
| 68 | 68 |
break |
| 69 | 69 |
} |
| 70 | 70 |
} |
| 71 | 71 |
if !gotOne {
|
| 72 |
- req.runConfig.Env = append(req.runConfig.Env, newVar) |
|
| 72 |
+ runConfig.Env = append(runConfig.Env, newVar) |
|
| 73 | 73 |
} |
| 74 | 74 |
} |
| 75 | 75 |
|
| 76 |
- return req.builder.commit(commitMessage.String()) |
|
| 76 |
+ return req.builder.commit(req.state, commitMessage.String()) |
|
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 | 79 |
// MAINTAINER some text <maybe@an.email.address> |
| ... | ... |
@@ -89,8 +90,8 @@ func maintainer(req dispatchRequest) error {
|
| 89 | 89 |
} |
| 90 | 90 |
|
| 91 | 91 |
maintainer := req.args[0] |
| 92 |
- req.builder.maintainer = maintainer |
|
| 93 |
- return req.builder.commit("MAINTAINER " + maintainer)
|
|
| 92 |
+ req.state.maintainer = maintainer |
|
| 93 |
+ return req.builder.commit(req.state, "MAINTAINER "+maintainer) |
|
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 | 96 |
// LABEL some json data describing the image |
| ... | ... |
@@ -111,26 +112,25 @@ func label(req dispatchRequest) error {
|
| 111 | 111 |
} |
| 112 | 112 |
|
| 113 | 113 |
commitStr := "LABEL" |
| 114 |
+ runConfig := req.state.runConfig |
|
| 114 | 115 |
|
| 115 |
- if req.runConfig.Labels == nil {
|
|
| 116 |
- req.runConfig.Labels = map[string]string{}
|
|
| 116 |
+ if runConfig.Labels == nil {
|
|
| 117 |
+ runConfig.Labels = map[string]string{}
|
|
| 117 | 118 |
} |
| 118 | 119 |
|
| 119 | 120 |
for j := 0; j < len(req.args); j++ {
|
| 120 |
- // name ==> req.args[j] |
|
| 121 |
- // value ==> req.args[j+1] |
|
| 122 |
- |
|
| 123 |
- if len(req.args[j]) == 0 {
|
|
| 121 |
+ name := req.args[j] |
|
| 122 |
+ if name == "" {
|
|
| 124 | 123 |
return errBlankCommandNames("LABEL")
|
| 125 | 124 |
} |
| 126 | 125 |
|
| 127 |
- newVar := req.args[j] + "=" + req.args[j+1] + "" |
|
| 128 |
- commitStr += " " + newVar |
|
| 126 |
+ value := req.args[j+1] |
|
| 127 |
+ commitStr += " " + name + "=" + value |
|
| 129 | 128 |
|
| 130 |
- req.runConfig.Labels[req.args[j]] = req.args[j+1] |
|
| 129 |
+ runConfig.Labels[name] = value |
|
| 131 | 130 |
j++ |
| 132 | 131 |
} |
| 133 |
- return req.builder.commit(commitStr) |
|
| 132 |
+ return req.builder.commit(req.state, commitStr) |
|
| 134 | 133 |
} |
| 135 | 134 |
|
| 136 | 135 |
// ADD foo /path |
| ... | ... |
@@ -147,7 +147,7 @@ func add(req dispatchRequest) error {
|
| 147 | 147 |
return err |
| 148 | 148 |
} |
| 149 | 149 |
|
| 150 |
- return req.builder.runContextCommand(req.args, true, true, "ADD", nil) |
|
| 150 |
+ return req.builder.runContextCommand(req, true, true, "ADD", nil) |
|
| 151 | 151 |
} |
| 152 | 152 |
|
| 153 | 153 |
// COPY foo /path |
| ... | ... |
@@ -174,13 +174,13 @@ func dispatchCopy(req dispatchRequest) error {
|
| 174 | 174 |
} |
| 175 | 175 |
} |
| 176 | 176 |
|
| 177 |
- return req.builder.runContextCommand(req.args, false, false, "COPY", im) |
|
| 177 |
+ return req.builder.runContextCommand(req, false, false, "COPY", im) |
|
| 178 | 178 |
} |
| 179 | 179 |
|
| 180 | 180 |
// FROM imagename[:tag | @digest] [AS build-stage-name] |
| 181 | 181 |
// |
| 182 | 182 |
func from(req dispatchRequest) error {
|
| 183 |
- ctxName, err := parseBuildStageName(req.args) |
|
| 183 |
+ stageName, err := parseBuildStageName(req.args) |
|
| 184 | 184 |
if err != nil {
|
| 185 | 185 |
return err |
| 186 | 186 |
} |
| ... | ... |
@@ -190,21 +190,23 @@ func from(req dispatchRequest) error {
|
| 190 | 190 |
} |
| 191 | 191 |
|
| 192 | 192 |
req.builder.resetImageCache() |
| 193 |
- if _, err := req.builder.imageContexts.add(ctxName); err != nil {
|
|
| 193 |
+ req.state.noBaseImage = false |
|
| 194 |
+ req.state.stageName = stageName |
|
| 195 |
+ if _, err := req.builder.imageContexts.add(stageName); err != nil {
|
|
| 194 | 196 |
return err |
| 195 | 197 |
} |
| 196 | 198 |
|
| 197 |
- image, err := req.builder.getFromImage(req.shlex, req.args[0]) |
|
| 199 |
+ image, err := req.builder.getFromImage(req.state, req.shlex, req.args[0]) |
|
| 198 | 200 |
if err != nil {
|
| 199 | 201 |
return err |
| 200 | 202 |
} |
| 201 | 203 |
if image != nil {
|
| 202 | 204 |
req.builder.imageContexts.update(image.ImageID(), image.RunConfig()) |
| 203 | 205 |
} |
| 204 |
- req.builder.from = image |
|
| 206 |
+ req.state.baseImage = image |
|
| 205 | 207 |
|
| 206 | 208 |
req.builder.buildArgs.ResetAllowed() |
| 207 |
- return req.builder.processImageFrom(image) |
|
| 209 |
+ return req.builder.processImageFrom(req.state, image) |
|
| 208 | 210 |
} |
| 209 | 211 |
|
| 210 | 212 |
func parseBuildStageName(args []string) (string, error) {
|
| ... | ... |
@@ -222,7 +224,7 @@ func parseBuildStageName(args []string) (string, error) {
|
| 222 | 222 |
return stageName, nil |
| 223 | 223 |
} |
| 224 | 224 |
|
| 225 |
-func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, error) {
|
|
| 225 |
+func (b *Builder) getFromImage(dispatchState *dispatchState, shlex *ShellLex, name string) (builder.Image, error) {
|
|
| 226 | 226 |
substitutionArgs := []string{}
|
| 227 | 227 |
for key, value := range b.buildArgs.GetAllMeta() {
|
| 228 | 228 |
substitutionArgs = append(substitutionArgs, key+"="+value) |
| ... | ... |
@@ -246,8 +248,8 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err |
| 246 | 246 |
if runtime.GOOS == "windows" {
|
| 247 | 247 |
return nil, errors.New("Windows does not support FROM scratch")
|
| 248 | 248 |
} |
| 249 |
- b.image = "" |
|
| 250 |
- b.noBaseImage = true |
|
| 249 |
+ dispatchState.imageID = "" |
|
| 250 |
+ dispatchState.noBaseImage = true |
|
| 251 | 251 |
return nil, nil |
| 252 | 252 |
} |
| 253 | 253 |
return pullOrGetImage(b, name) |
| ... | ... |
@@ -279,9 +281,10 @@ func onbuild(req dispatchRequest) error {
|
| 279 | 279 |
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
| 280 | 280 |
} |
| 281 | 281 |
|
| 282 |
+ runConfig := req.state.runConfig |
|
| 282 | 283 |
original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "") |
| 283 |
- req.runConfig.OnBuild = append(req.runConfig.OnBuild, original) |
|
| 284 |
- return req.builder.commit("ONBUILD " + original)
|
|
| 284 |
+ runConfig.OnBuild = append(runConfig.OnBuild, original) |
|
| 285 |
+ return req.builder.commit(req.state, "ONBUILD "+original) |
|
| 285 | 286 |
} |
| 286 | 287 |
|
| 287 | 288 |
// WORKDIR /tmp |
| ... | ... |
@@ -298,9 +301,10 @@ func workdir(req dispatchRequest) error {
|
| 298 | 298 |
return err |
| 299 | 299 |
} |
| 300 | 300 |
|
| 301 |
+ runConfig := req.state.runConfig |
|
| 301 | 302 |
// This is from the Dockerfile and will not necessarily be in platform |
| 302 | 303 |
// specific semantics, hence ensure it is converted. |
| 303 |
- req.runConfig.WorkingDir, err = normaliseWorkdir(req.runConfig.WorkingDir, req.args[0]) |
|
| 304 |
+ runConfig.WorkingDir, err = normaliseWorkdir(runConfig.WorkingDir, req.args[0]) |
|
| 304 | 305 |
if err != nil {
|
| 305 | 306 |
return err |
| 306 | 307 |
} |
| ... | ... |
@@ -315,9 +319,9 @@ func workdir(req dispatchRequest) error {
|
| 315 | 315 |
return nil |
| 316 | 316 |
} |
| 317 | 317 |
|
| 318 |
- comment := "WORKDIR " + req.runConfig.WorkingDir |
|
| 319 |
- runConfigWithCommentCmd := copyRunConfig(req.runConfig, withCmdCommentString(comment)) |
|
| 320 |
- if hit, err := req.builder.probeCache(req.builder.image, runConfigWithCommentCmd); err != nil || hit {
|
|
| 318 |
+ comment := "WORKDIR " + runConfig.WorkingDir |
|
| 319 |
+ runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment)) |
|
| 320 |
+ if hit, err := req.builder.probeCache(req.state, runConfigWithCommentCmd); err != nil || hit {
|
|
| 321 | 321 |
return err |
| 322 | 322 |
} |
| 323 | 323 |
|
| ... | ... |
@@ -334,7 +338,7 @@ func workdir(req dispatchRequest) error {
|
| 334 | 334 |
return err |
| 335 | 335 |
} |
| 336 | 336 |
|
| 337 |
- return req.builder.commitContainer(container.ID, runConfigWithCommentCmd) |
|
| 337 |
+ return req.builder.commitContainer(req.state, container.ID, runConfigWithCommentCmd) |
|
| 338 | 338 |
} |
| 339 | 339 |
|
| 340 | 340 |
// RUN some command yo |
| ... | ... |
@@ -348,7 +352,7 @@ func workdir(req dispatchRequest) error {
|
| 348 | 348 |
// RUN [ "echo", "hi" ] # echo hi |
| 349 | 349 |
// |
| 350 | 350 |
func run(req dispatchRequest) error {
|
| 351 |
- if !req.builder.hasFromImage() {
|
|
| 351 |
+ if !req.state.hasFromImage() {
|
|
| 352 | 352 |
return errors.New("Please provide a source image with `from` prior to run")
|
| 353 | 353 |
} |
| 354 | 354 |
|
| ... | ... |
@@ -356,29 +360,30 @@ func run(req dispatchRequest) error {
|
| 356 | 356 |
return err |
| 357 | 357 |
} |
| 358 | 358 |
|
| 359 |
+ stateRunConfig := req.state.runConfig |
|
| 359 | 360 |
args := handleJSONArgs(req.args, req.attributes) |
| 360 | 361 |
if !req.attributes["json"] {
|
| 361 |
- args = append(getShell(req.runConfig), args...) |
|
| 362 |
+ args = append(getShell(stateRunConfig), args...) |
|
| 362 | 363 |
} |
| 363 | 364 |
cmdFromArgs := strslice.StrSlice(args) |
| 364 |
- buildArgs := req.builder.buildArgsWithoutConfigEnv() |
|
| 365 |
+ buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env) |
|
| 365 | 366 |
|
| 366 | 367 |
saveCmd := cmdFromArgs |
| 367 | 368 |
if len(buildArgs) > 0 {
|
| 368 | 369 |
saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs) |
| 369 | 370 |
} |
| 370 | 371 |
|
| 371 |
- runConfigForCacheProbe := copyRunConfig(req.runConfig, |
|
| 372 |
+ runConfigForCacheProbe := copyRunConfig(stateRunConfig, |
|
| 372 | 373 |
withCmd(saveCmd), |
| 373 | 374 |
withEntrypointOverride(saveCmd, nil)) |
| 374 |
- hit, err := req.builder.probeCache(req.builder.image, runConfigForCacheProbe) |
|
| 375 |
+ hit, err := req.builder.probeCache(req.state, runConfigForCacheProbe) |
|
| 375 | 376 |
if err != nil || hit {
|
| 376 | 377 |
return err |
| 377 | 378 |
} |
| 378 | 379 |
|
| 379 |
- runConfig := copyRunConfig(req.runConfig, |
|
| 380 |
+ runConfig := copyRunConfig(stateRunConfig, |
|
| 380 | 381 |
withCmd(cmdFromArgs), |
| 381 |
- withEnv(append(req.runConfig.Env, buildArgs...)), |
|
| 382 |
+ withEnv(append(stateRunConfig.Env, buildArgs...)), |
|
| 382 | 383 |
withEntrypointOverride(saveCmd, strslice.StrSlice{""}))
|
| 383 | 384 |
|
| 384 | 385 |
// set config as already being escaped, this prevents double escaping on windows |
| ... | ... |
@@ -393,7 +398,7 @@ func run(req dispatchRequest) error {
|
| 393 | 393 |
return err |
| 394 | 394 |
} |
| 395 | 395 |
|
| 396 |
- return req.builder.commitContainer(cID, runConfigForCacheProbe) |
|
| 396 |
+ return req.builder.commitContainer(req.state, cID, runConfigForCacheProbe) |
|
| 397 | 397 |
} |
| 398 | 398 |
|
| 399 | 399 |
// Derive the command to use for probeCache() and to commit in this container. |
| ... | ... |
@@ -431,22 +436,22 @@ func cmd(req dispatchRequest) error {
|
| 431 | 431 |
return err |
| 432 | 432 |
} |
| 433 | 433 |
|
| 434 |
+ runConfig := req.state.runConfig |
|
| 434 | 435 |
cmdSlice := handleJSONArgs(req.args, req.attributes) |
| 435 |
- |
|
| 436 | 436 |
if !req.attributes["json"] {
|
| 437 |
- cmdSlice = append(getShell(req.runConfig), cmdSlice...) |
|
| 437 |
+ cmdSlice = append(getShell(runConfig), cmdSlice...) |
|
| 438 | 438 |
} |
| 439 | 439 |
|
| 440 |
- req.runConfig.Cmd = strslice.StrSlice(cmdSlice) |
|
| 440 |
+ runConfig.Cmd = strslice.StrSlice(cmdSlice) |
|
| 441 | 441 |
// set config as already being escaped, this prevents double escaping on windows |
| 442 |
- req.runConfig.ArgsEscaped = true |
|
| 442 |
+ runConfig.ArgsEscaped = true |
|
| 443 | 443 |
|
| 444 |
- if err := req.builder.commit(fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
|
| 444 |
+ if err := req.builder.commit(req.state, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
|
| 445 | 445 |
return err |
| 446 | 446 |
} |
| 447 | 447 |
|
| 448 | 448 |
if len(req.args) != 0 {
|
| 449 |
- req.builder.cmdSet = true |
|
| 449 |
+ req.state.cmdSet = true |
|
| 450 | 450 |
} |
| 451 | 451 |
|
| 452 | 452 |
return nil |
| ... | ... |
@@ -478,6 +483,7 @@ func healthcheck(req dispatchRequest) error {
|
| 478 | 478 |
if len(req.args) == 0 {
|
| 479 | 479 |
return errAtLeastOneArgument("HEALTHCHECK")
|
| 480 | 480 |
} |
| 481 |
+ runConfig := req.state.runConfig |
|
| 481 | 482 |
typ := strings.ToUpper(req.args[0]) |
| 482 | 483 |
args := req.args[1:] |
| 483 | 484 |
if typ == "NONE" {
|
| ... | ... |
@@ -485,12 +491,12 @@ func healthcheck(req dispatchRequest) error {
|
| 485 | 485 |
return errors.New("HEALTHCHECK NONE takes no arguments")
|
| 486 | 486 |
} |
| 487 | 487 |
test := strslice.StrSlice{typ}
|
| 488 |
- req.runConfig.Healthcheck = &container.HealthConfig{
|
|
| 488 |
+ runConfig.Healthcheck = &container.HealthConfig{
|
|
| 489 | 489 |
Test: test, |
| 490 | 490 |
} |
| 491 | 491 |
} else {
|
| 492 |
- if req.runConfig.Healthcheck != nil {
|
|
| 493 |
- oldCmd := req.runConfig.Healthcheck.Test |
|
| 492 |
+ if runConfig.Healthcheck != nil {
|
|
| 493 |
+ oldCmd := runConfig.Healthcheck.Test |
|
| 494 | 494 |
if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
|
| 495 | 495 |
fmt.Fprintf(req.builder.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd) |
| 496 | 496 |
} |
| ... | ... |
@@ -554,10 +560,10 @@ func healthcheck(req dispatchRequest) error {
|
| 554 | 554 |
healthcheck.Retries = 0 |
| 555 | 555 |
} |
| 556 | 556 |
|
| 557 |
- req.runConfig.Healthcheck = &healthcheck |
|
| 557 |
+ runConfig.Healthcheck = &healthcheck |
|
| 558 | 558 |
} |
| 559 | 559 |
|
| 560 |
- return req.builder.commit(fmt.Sprintf("HEALTHCHECK %q", req.runConfig.Healthcheck))
|
|
| 560 |
+ return req.builder.commit(req.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck))
|
|
| 561 | 561 |
} |
| 562 | 562 |
|
| 563 | 563 |
// ENTRYPOINT /usr/sbin/nginx |
| ... | ... |
@@ -573,27 +579,28 @@ func entrypoint(req dispatchRequest) error {
|
| 573 | 573 |
return err |
| 574 | 574 |
} |
| 575 | 575 |
|
| 576 |
+ runConfig := req.state.runConfig |
|
| 576 | 577 |
parsed := handleJSONArgs(req.args, req.attributes) |
| 577 | 578 |
|
| 578 | 579 |
switch {
|
| 579 | 580 |
case req.attributes["json"]: |
| 580 | 581 |
// ENTRYPOINT ["echo", "hi"] |
| 581 |
- req.runConfig.Entrypoint = strslice.StrSlice(parsed) |
|
| 582 |
+ runConfig.Entrypoint = strslice.StrSlice(parsed) |
|
| 582 | 583 |
case len(parsed) == 0: |
| 583 | 584 |
// ENTRYPOINT [] |
| 584 |
- req.runConfig.Entrypoint = nil |
|
| 585 |
+ runConfig.Entrypoint = nil |
|
| 585 | 586 |
default: |
| 586 | 587 |
// ENTRYPOINT echo hi |
| 587 |
- req.runConfig.Entrypoint = strslice.StrSlice(append(getShell(req.runConfig), parsed[0])) |
|
| 588 |
+ runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig), parsed[0])) |
|
| 588 | 589 |
} |
| 589 | 590 |
|
| 590 | 591 |
// when setting the entrypoint if a CMD was not explicitly set then |
| 591 | 592 |
// set the command to nil |
| 592 |
- if !req.builder.cmdSet {
|
|
| 593 |
- req.runConfig.Cmd = nil |
|
| 593 |
+ if !req.state.cmdSet {
|
|
| 594 |
+ runConfig.Cmd = nil |
|
| 594 | 595 |
} |
| 595 | 596 |
|
| 596 |
- return req.builder.commit(fmt.Sprintf("ENTRYPOINT %q", req.runConfig.Entrypoint))
|
|
| 597 |
+ return req.builder.commit(req.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint))
|
|
| 597 | 598 |
} |
| 598 | 599 |
|
| 599 | 600 |
// EXPOSE 6667/tcp 7000/tcp |
| ... | ... |
@@ -612,8 +619,9 @@ func expose(req dispatchRequest) error {
|
| 612 | 612 |
return err |
| 613 | 613 |
} |
| 614 | 614 |
|
| 615 |
- if req.runConfig.ExposedPorts == nil {
|
|
| 616 |
- req.runConfig.ExposedPorts = make(nat.PortSet) |
|
| 615 |
+ runConfig := req.state.runConfig |
|
| 616 |
+ if runConfig.ExposedPorts == nil {
|
|
| 617 |
+ runConfig.ExposedPorts = make(nat.PortSet) |
|
| 617 | 618 |
} |
| 618 | 619 |
|
| 619 | 620 |
ports, _, err := nat.ParsePortSpecs(portsTab) |
| ... | ... |
@@ -627,14 +635,14 @@ func expose(req dispatchRequest) error {
|
| 627 | 627 |
portList := make([]string, len(ports)) |
| 628 | 628 |
var i int |
| 629 | 629 |
for port := range ports {
|
| 630 |
- if _, exists := req.runConfig.ExposedPorts[port]; !exists {
|
|
| 631 |
- req.runConfig.ExposedPorts[port] = struct{}{}
|
|
| 630 |
+ if _, exists := runConfig.ExposedPorts[port]; !exists {
|
|
| 631 |
+ runConfig.ExposedPorts[port] = struct{}{}
|
|
| 632 | 632 |
} |
| 633 | 633 |
portList[i] = string(port) |
| 634 | 634 |
i++ |
| 635 | 635 |
} |
| 636 | 636 |
sort.Strings(portList) |
| 637 |
- return req.builder.commit("EXPOSE " + strings.Join(portList, " "))
|
|
| 637 |
+ return req.builder.commit(req.state, "EXPOSE "+strings.Join(portList, " ")) |
|
| 638 | 638 |
} |
| 639 | 639 |
|
| 640 | 640 |
// USER foo |
| ... | ... |
@@ -651,8 +659,8 @@ func user(req dispatchRequest) error {
|
| 651 | 651 |
return err |
| 652 | 652 |
} |
| 653 | 653 |
|
| 654 |
- req.runConfig.User = req.args[0] |
|
| 655 |
- return req.builder.commit(fmt.Sprintf("USER %v", req.args))
|
|
| 654 |
+ req.state.runConfig.User = req.args[0] |
|
| 655 |
+ return req.builder.commit(req.state, fmt.Sprintf("USER %v", req.args))
|
|
| 656 | 656 |
} |
| 657 | 657 |
|
| 658 | 658 |
// VOLUME /foo |
| ... | ... |
@@ -668,17 +676,18 @@ func volume(req dispatchRequest) error {
|
| 668 | 668 |
return err |
| 669 | 669 |
} |
| 670 | 670 |
|
| 671 |
- if req.runConfig.Volumes == nil {
|
|
| 672 |
- req.runConfig.Volumes = map[string]struct{}{}
|
|
| 671 |
+ runConfig := req.state.runConfig |
|
| 672 |
+ if runConfig.Volumes == nil {
|
|
| 673 |
+ runConfig.Volumes = map[string]struct{}{}
|
|
| 673 | 674 |
} |
| 674 | 675 |
for _, v := range req.args {
|
| 675 | 676 |
v = strings.TrimSpace(v) |
| 676 | 677 |
if v == "" {
|
| 677 | 678 |
return errors.New("VOLUME specified can not be an empty string")
|
| 678 | 679 |
} |
| 679 |
- req.runConfig.Volumes[v] = struct{}{}
|
|
| 680 |
+ runConfig.Volumes[v] = struct{}{}
|
|
| 680 | 681 |
} |
| 681 |
- return req.builder.commit(fmt.Sprintf("VOLUME %v", req.args))
|
|
| 682 |
+ return req.builder.commit(req.state, fmt.Sprintf("VOLUME %v", req.args))
|
|
| 682 | 683 |
} |
| 683 | 684 |
|
| 684 | 685 |
// STOPSIGNAL signal |
| ... | ... |
@@ -695,8 +704,8 @@ func stopSignal(req dispatchRequest) error {
|
| 695 | 695 |
return err |
| 696 | 696 |
} |
| 697 | 697 |
|
| 698 |
- req.runConfig.StopSignal = sig |
|
| 699 |
- return req.builder.commit(fmt.Sprintf("STOPSIGNAL %v", req.args))
|
|
| 698 |
+ req.state.runConfig.StopSignal = sig |
|
| 699 |
+ return req.builder.commit(req.state, fmt.Sprintf("STOPSIGNAL %v", req.args))
|
|
| 700 | 700 |
} |
| 701 | 701 |
|
| 702 | 702 |
// ARG name[=value] |
| ... | ... |
@@ -742,11 +751,11 @@ func arg(req dispatchRequest) error {
|
| 742 | 742 |
req.builder.buildArgs.AddArg(name, value) |
| 743 | 743 |
|
| 744 | 744 |
// Arg before FROM doesn't add a layer |
| 745 |
- if !req.builder.hasFromImage() {
|
|
| 745 |
+ if !req.state.hasFromImage() {
|
|
| 746 | 746 |
req.builder.buildArgs.AddMetaArg(name, value) |
| 747 | 747 |
return nil |
| 748 | 748 |
} |
| 749 |
- return req.builder.commit("ARG " + arg)
|
|
| 749 |
+ return req.builder.commit(req.state, "ARG "+arg) |
|
| 750 | 750 |
} |
| 751 | 751 |
|
| 752 | 752 |
// SHELL powershell -command |
| ... | ... |
@@ -763,12 +772,12 @@ func shell(req dispatchRequest) error {
|
| 763 | 763 |
return errAtLeastOneArgument("SHELL")
|
| 764 | 764 |
case req.attributes["json"]: |
| 765 | 765 |
// SHELL ["powershell", "-command"] |
| 766 |
- req.runConfig.Shell = strslice.StrSlice(shellSlice) |
|
| 766 |
+ req.state.runConfig.Shell = strslice.StrSlice(shellSlice) |
|
| 767 | 767 |
default: |
| 768 | 768 |
// SHELL powershell -command - not JSON |
| 769 | 769 |
return errNotJSON("SHELL", req.original)
|
| 770 | 770 |
} |
| 771 |
- return req.builder.commit(fmt.Sprintf("SHELL %v", shellSlice))
|
|
| 771 |
+ return req.builder.commit(req.state, fmt.Sprintf("SHELL %v", shellSlice))
|
|
| 772 | 772 |
} |
| 773 | 773 |
|
| 774 | 774 |
func errAtLeastOneArgument(command string) error {
|
| ... | ... |
@@ -26,7 +26,7 @@ type commandWithFunction struct {
|
| 26 | 26 |
|
| 27 | 27 |
func withArgs(f dispatcher) func([]string) error {
|
| 28 | 28 |
return func(args []string) error {
|
| 29 |
- return f(dispatchRequest{args: args, runConfig: &container.Config{}})
|
|
| 29 |
+ return f(dispatchRequest{args: args})
|
|
| 30 | 30 |
} |
| 31 | 31 |
} |
| 32 | 32 |
|
| ... | ... |
@@ -38,17 +38,16 @@ func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error {
|
| 38 | 38 |
|
| 39 | 39 |
func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
|
| 40 | 40 |
return dispatchRequest{
|
| 41 |
- builder: builder, |
|
| 42 |
- args: args, |
|
| 43 |
- flags: NewBFlags(), |
|
| 44 |
- runConfig: &container.Config{},
|
|
| 45 |
- shlex: NewShellLex(parser.DefaultEscapeToken), |
|
| 41 |
+ builder: builder, |
|
| 42 |
+ args: args, |
|
| 43 |
+ flags: NewBFlags(), |
|
| 44 |
+ shlex: NewShellLex(parser.DefaultEscapeToken), |
|
| 45 |
+ state: &dispatchState{runConfig: &container.Config{}},
|
|
| 46 | 46 |
} |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 | 49 |
func newBuilderWithMockBackend() *Builder {
|
| 50 | 50 |
b := &Builder{
|
| 51 |
- runConfig: &container.Config{},
|
|
| 52 | 51 |
options: &types.ImageBuildOptions{},
|
| 53 | 52 |
docker: &MockBackend{},
|
| 54 | 53 |
buildArgs: newBuildArgs(make(map[string]*string)), |
| ... | ... |
@@ -138,7 +137,7 @@ func TestEnv2Variables(t *testing.T) {
|
| 138 | 138 |
fmt.Sprintf("%s=%s", args[0], args[1]),
|
| 139 | 139 |
fmt.Sprintf("%s=%s", args[2], args[3]),
|
| 140 | 140 |
} |
| 141 |
- assert.Equal(t, expected, req.runConfig.Env) |
|
| 141 |
+ assert.Equal(t, expected, req.state.runConfig.Env) |
|
| 142 | 142 |
} |
| 143 | 143 |
|
| 144 | 144 |
func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
|
| ... | ... |
@@ -146,7 +145,7 @@ func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
|
| 146 | 146 |
|
| 147 | 147 |
args := []string{"var1", "val1"}
|
| 148 | 148 |
req := defaultDispatchReq(b, args...) |
| 149 |
- req.runConfig.Env = []string{"var1=old", "var2=fromenv"}
|
|
| 149 |
+ req.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
|
|
| 150 | 150 |
err := env(req) |
| 151 | 151 |
require.NoError(t, err) |
| 152 | 152 |
|
| ... | ... |
@@ -154,16 +153,17 @@ func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
|
| 154 | 154 |
fmt.Sprintf("%s=%s", args[0], args[1]),
|
| 155 | 155 |
"var2=fromenv", |
| 156 | 156 |
} |
| 157 |
- assert.Equal(t, expected, req.runConfig.Env) |
|
| 157 |
+ assert.Equal(t, expected, req.state.runConfig.Env) |
|
| 158 | 158 |
} |
| 159 | 159 |
|
| 160 | 160 |
func TestMaintainer(t *testing.T) {
|
| 161 | 161 |
maintainerEntry := "Some Maintainer <maintainer@example.com>" |
| 162 | 162 |
|
| 163 | 163 |
b := newBuilderWithMockBackend() |
| 164 |
- err := maintainer(defaultDispatchReq(b, maintainerEntry)) |
|
| 164 |
+ req := defaultDispatchReq(b, maintainerEntry) |
|
| 165 |
+ err := maintainer(req) |
|
| 165 | 166 |
require.NoError(t, err) |
| 166 |
- assert.Equal(t, maintainerEntry, b.maintainer) |
|
| 167 |
+ assert.Equal(t, maintainerEntry, req.state.maintainer) |
|
| 167 | 168 |
} |
| 168 | 169 |
|
| 169 | 170 |
func TestLabel(t *testing.T) {
|
| ... | ... |
@@ -176,13 +176,14 @@ func TestLabel(t *testing.T) {
|
| 176 | 176 |
err := label(req) |
| 177 | 177 |
require.NoError(t, err) |
| 178 | 178 |
|
| 179 |
- require.Contains(t, req.runConfig.Labels, labelName) |
|
| 180 |
- assert.Equal(t, req.runConfig.Labels[labelName], labelValue) |
|
| 179 |
+ require.Contains(t, req.state.runConfig.Labels, labelName) |
|
| 180 |
+ assert.Equal(t, req.state.runConfig.Labels[labelName], labelValue) |
|
| 181 | 181 |
} |
| 182 | 182 |
|
| 183 | 183 |
func TestFromScratch(t *testing.T) {
|
| 184 | 184 |
b := newBuilderWithMockBackend() |
| 185 |
- err := from(defaultDispatchReq(b, "scratch")) |
|
| 185 |
+ req := defaultDispatchReq(b, "scratch") |
|
| 186 |
+ err := from(req) |
|
| 186 | 187 |
|
| 187 | 188 |
if runtime.GOOS == "windows" {
|
| 188 | 189 |
assert.EqualError(t, err, "Windows does not support FROM scratch") |
| ... | ... |
@@ -190,8 +191,8 @@ func TestFromScratch(t *testing.T) {
|
| 190 | 190 |
} |
| 191 | 191 |
|
| 192 | 192 |
require.NoError(t, err) |
| 193 |
- assert.Equal(t, "", b.image) |
|
| 194 |
- assert.Equal(t, true, b.noBaseImage) |
|
| 193 |
+ assert.Equal(t, "", req.state.imageID) |
|
| 194 |
+ assert.Equal(t, true, req.state.noBaseImage) |
|
| 195 | 195 |
} |
| 196 | 196 |
|
| 197 | 197 |
func TestFromWithArg(t *testing.T) {
|
| ... | ... |
@@ -205,11 +206,12 @@ func TestFromWithArg(t *testing.T) {
|
| 205 | 205 |
b.docker.(*MockBackend).getImageOnBuildFunc = getImage |
| 206 | 206 |
|
| 207 | 207 |
require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag))) |
| 208 |
- err := from(defaultDispatchReq(b, "alpine${THETAG}"))
|
|
| 208 |
+ req := defaultDispatchReq(b, "alpine${THETAG}")
|
|
| 209 |
+ err := from(req) |
|
| 209 | 210 |
|
| 210 | 211 |
require.NoError(t, err) |
| 211 |
- assert.Equal(t, expected, b.image) |
|
| 212 |
- assert.Equal(t, expected, b.from.ImageID()) |
|
| 212 |
+ assert.Equal(t, expected, req.state.imageID) |
|
| 213 |
+ assert.Equal(t, expected, req.state.baseImage.ImageID()) |
|
| 213 | 214 |
assert.Len(t, b.buildArgs.GetAllAllowed(), 0) |
| 214 | 215 |
assert.Len(t, b.buildArgs.GetAllMeta(), 1) |
| 215 | 216 |
} |
| ... | ... |
@@ -225,9 +227,10 @@ func TestFromWithUndefinedArg(t *testing.T) {
|
| 225 | 225 |
b.docker.(*MockBackend).getImageOnBuildFunc = getImage |
| 226 | 226 |
b.options.BuildArgs = map[string]*string{"THETAG": &tag}
|
| 227 | 227 |
|
| 228 |
- err := from(defaultDispatchReq(b, "alpine${THETAG}"))
|
|
| 228 |
+ req := defaultDispatchReq(b, "alpine${THETAG}")
|
|
| 229 |
+ err := from(req) |
|
| 229 | 230 |
require.NoError(t, err) |
| 230 |
- assert.Equal(t, expected, b.image) |
|
| 231 |
+ assert.Equal(t, expected, req.state.imageID) |
|
| 231 | 232 |
} |
| 232 | 233 |
|
| 233 | 234 |
func TestOnbuildIllegalTriggers(t *testing.T) {
|
| ... | ... |
@@ -249,11 +252,11 @@ func TestOnbuild(t *testing.T) {
|
| 249 | 249 |
|
| 250 | 250 |
req := defaultDispatchReq(b, "ADD", ".", "/app/src") |
| 251 | 251 |
req.original = "ONBUILD ADD . /app/src" |
| 252 |
- req.runConfig = &container.Config{}
|
|
| 252 |
+ req.state.runConfig = &container.Config{}
|
|
| 253 | 253 |
|
| 254 | 254 |
err := onbuild(req) |
| 255 | 255 |
require.NoError(t, err) |
| 256 |
- assert.Equal(t, "ADD . /app/src", req.runConfig.OnBuild[0]) |
|
| 256 |
+ assert.Equal(t, "ADD . /app/src", req.state.runConfig.OnBuild[0]) |
|
| 257 | 257 |
} |
| 258 | 258 |
|
| 259 | 259 |
func TestWorkdir(t *testing.T) {
|
| ... | ... |
@@ -266,7 +269,7 @@ func TestWorkdir(t *testing.T) {
|
| 266 | 266 |
req := defaultDispatchReq(b, workingDir) |
| 267 | 267 |
err := workdir(req) |
| 268 | 268 |
require.NoError(t, err) |
| 269 |
- assert.Equal(t, workingDir, req.runConfig.WorkingDir) |
|
| 269 |
+ assert.Equal(t, workingDir, req.state.runConfig.WorkingDir) |
|
| 270 | 270 |
} |
| 271 | 271 |
|
| 272 | 272 |
func TestCmd(t *testing.T) {
|
| ... | ... |
@@ -284,8 +287,8 @@ func TestCmd(t *testing.T) {
|
| 284 | 284 |
expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
|
| 285 | 285 |
} |
| 286 | 286 |
|
| 287 |
- assert.Equal(t, expectedCommand, req.runConfig.Cmd) |
|
| 288 |
- assert.True(t, b.cmdSet) |
|
| 287 |
+ assert.Equal(t, expectedCommand, req.state.runConfig.Cmd) |
|
| 288 |
+ assert.True(t, req.state.cmdSet) |
|
| 289 | 289 |
} |
| 290 | 290 |
|
| 291 | 291 |
func TestHealthcheckNone(t *testing.T) {
|
| ... | ... |
@@ -295,8 +298,8 @@ func TestHealthcheckNone(t *testing.T) {
|
| 295 | 295 |
err := healthcheck(req) |
| 296 | 296 |
require.NoError(t, err) |
| 297 | 297 |
|
| 298 |
- require.NotNil(t, req.runConfig.Healthcheck) |
|
| 299 |
- assert.Equal(t, []string{"NONE"}, req.runConfig.Healthcheck.Test)
|
|
| 298 |
+ require.NotNil(t, req.state.runConfig.Healthcheck) |
|
| 299 |
+ assert.Equal(t, []string{"NONE"}, req.state.runConfig.Healthcheck.Test)
|
|
| 300 | 300 |
} |
| 301 | 301 |
|
| 302 | 302 |
func TestHealthcheckCmd(t *testing.T) {
|
| ... | ... |
@@ -307,9 +310,9 @@ func TestHealthcheckCmd(t *testing.T) {
|
| 307 | 307 |
err := healthcheck(req) |
| 308 | 308 |
require.NoError(t, err) |
| 309 | 309 |
|
| 310 |
- require.NotNil(t, req.runConfig.Healthcheck) |
|
| 310 |
+ require.NotNil(t, req.state.runConfig.Healthcheck) |
|
| 311 | 311 |
expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
|
| 312 |
- assert.Equal(t, expectedTest, req.runConfig.Healthcheck.Test) |
|
| 312 |
+ assert.Equal(t, expectedTest, req.state.runConfig.Healthcheck.Test) |
|
| 313 | 313 |
} |
| 314 | 314 |
|
| 315 | 315 |
func TestEntrypoint(t *testing.T) {
|
| ... | ... |
@@ -319,7 +322,7 @@ func TestEntrypoint(t *testing.T) {
|
| 319 | 319 |
req := defaultDispatchReq(b, entrypointCmd) |
| 320 | 320 |
err := entrypoint(req) |
| 321 | 321 |
require.NoError(t, err) |
| 322 |
- require.NotNil(t, req.runConfig.Entrypoint) |
|
| 322 |
+ require.NotNil(t, req.state.runConfig.Entrypoint) |
|
| 323 | 323 |
|
| 324 | 324 |
var expectedEntrypoint strslice.StrSlice |
| 325 | 325 |
if runtime.GOOS == "windows" {
|
| ... | ... |
@@ -327,7 +330,7 @@ func TestEntrypoint(t *testing.T) {
|
| 327 | 327 |
} else {
|
| 328 | 328 |
expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
|
| 329 | 329 |
} |
| 330 |
- assert.Equal(t, expectedEntrypoint, req.runConfig.Entrypoint) |
|
| 330 |
+ assert.Equal(t, expectedEntrypoint, req.state.runConfig.Entrypoint) |
|
| 331 | 331 |
} |
| 332 | 332 |
|
| 333 | 333 |
func TestExpose(t *testing.T) {
|
| ... | ... |
@@ -338,12 +341,12 @@ func TestExpose(t *testing.T) {
|
| 338 | 338 |
err := expose(req) |
| 339 | 339 |
require.NoError(t, err) |
| 340 | 340 |
|
| 341 |
- require.NotNil(t, req.runConfig.ExposedPorts) |
|
| 342 |
- require.Len(t, req.runConfig.ExposedPorts, 1) |
|
| 341 |
+ require.NotNil(t, req.state.runConfig.ExposedPorts) |
|
| 342 |
+ require.Len(t, req.state.runConfig.ExposedPorts, 1) |
|
| 343 | 343 |
|
| 344 | 344 |
portsMapping, err := nat.ParsePortSpec(exposedPort) |
| 345 | 345 |
require.NoError(t, err) |
| 346 |
- assert.Contains(t, req.runConfig.ExposedPorts, portsMapping[0].Port) |
|
| 346 |
+ assert.Contains(t, req.state.runConfig.ExposedPorts, portsMapping[0].Port) |
|
| 347 | 347 |
} |
| 348 | 348 |
|
| 349 | 349 |
func TestUser(t *testing.T) {
|
| ... | ... |
@@ -353,7 +356,7 @@ func TestUser(t *testing.T) {
|
| 353 | 353 |
req := defaultDispatchReq(b, userCommand) |
| 354 | 354 |
err := user(req) |
| 355 | 355 |
require.NoError(t, err) |
| 356 |
- assert.Equal(t, userCommand, req.runConfig.User) |
|
| 356 |
+ assert.Equal(t, userCommand, req.state.runConfig.User) |
|
| 357 | 357 |
} |
| 358 | 358 |
|
| 359 | 359 |
func TestVolume(t *testing.T) {
|
| ... | ... |
@@ -365,9 +368,9 @@ func TestVolume(t *testing.T) {
|
| 365 | 365 |
err := volume(req) |
| 366 | 366 |
require.NoError(t, err) |
| 367 | 367 |
|
| 368 |
- require.NotNil(t, req.runConfig.Volumes) |
|
| 369 |
- assert.Len(t, req.runConfig.Volumes, 1) |
|
| 370 |
- assert.Contains(t, req.runConfig.Volumes, exposedVolume) |
|
| 368 |
+ require.NotNil(t, req.state.runConfig.Volumes) |
|
| 369 |
+ assert.Len(t, req.state.runConfig.Volumes, 1) |
|
| 370 |
+ assert.Contains(t, req.state.runConfig.Volumes, exposedVolume) |
|
| 371 | 371 |
} |
| 372 | 372 |
|
| 373 | 373 |
func TestStopSignal(t *testing.T) {
|
| ... | ... |
@@ -377,7 +380,7 @@ func TestStopSignal(t *testing.T) {
|
| 377 | 377 |
req := defaultDispatchReq(b, signal) |
| 378 | 378 |
err := stopSignal(req) |
| 379 | 379 |
require.NoError(t, err) |
| 380 |
- assert.Equal(t, signal, req.runConfig.StopSignal) |
|
| 380 |
+ assert.Equal(t, signal, req.state.runConfig.StopSignal) |
|
| 381 | 381 |
} |
| 382 | 382 |
|
| 383 | 383 |
func TestArg(t *testing.T) {
|
| ... | ... |
@@ -405,7 +408,7 @@ func TestShell(t *testing.T) {
|
| 405 | 405 |
require.NoError(t, err) |
| 406 | 406 |
|
| 407 | 407 |
expectedShell := strslice.StrSlice([]string{shellCmd})
|
| 408 |
- assert.Equal(t, expectedShell, req.runConfig.Shell) |
|
| 408 |
+ assert.Equal(t, expectedShell, req.state.runConfig.Shell) |
|
| 409 | 409 |
} |
| 410 | 410 |
|
| 411 | 411 |
func TestParseOptInterval(t *testing.T) {
|
| ... | ... |
@@ -439,8 +442,9 @@ func TestRunWithBuildArgs(t *testing.T) {
|
| 439 | 439 |
b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
|
| 440 | 440 |
b.disableCommit = false |
| 441 | 441 |
|
| 442 |
+ runConfig := &container.Config{}
|
|
| 442 | 443 |
origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
|
| 443 |
- cmdWithShell := strslice.StrSlice(append(getShell(b.runConfig), "echo foo")) |
|
| 444 |
+ cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo")) |
|
| 444 | 445 |
envVars := []string{"|1", "one=two"}
|
| 445 | 446 |
cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...)) |
| 446 | 447 |
|
| ... | ... |
@@ -477,12 +481,10 @@ func TestRunWithBuildArgs(t *testing.T) {
|
| 477 | 477 |
req := defaultDispatchReq(b, "abcdef") |
| 478 | 478 |
require.NoError(t, from(req)) |
| 479 | 479 |
b.buildArgs.AddArg("one", strPtr("two"))
|
| 480 |
- // TODO: this can be removed with b.runConfig |
|
| 481 |
- req.runConfig.Cmd = origCmd |
|
| 482 | 480 |
|
| 483 | 481 |
req.args = []string{"echo foo"}
|
| 484 | 482 |
require.NoError(t, run(req)) |
| 485 | 483 |
|
| 486 | 484 |
// Check that runConfig.Cmd has not been modified by run |
| 487 |
- assert.Equal(t, origCmd, b.runConfig.Cmd) |
|
| 485 |
+ assert.Equal(t, origCmd, req.state.runConfig.Cmd) |
|
| 488 | 486 |
} |
| ... | ... |
@@ -25,6 +25,7 @@ import ( |
| 25 | 25 |
"strings" |
| 26 | 26 |
|
| 27 | 27 |
"github.com/docker/docker/api/types/container" |
| 28 |
+ "github.com/docker/docker/builder" |
|
| 28 | 29 |
"github.com/docker/docker/builder/dockerfile/command" |
| 29 | 30 |
"github.com/docker/docker/builder/dockerfile/parser" |
| 30 | 31 |
"github.com/docker/docker/runconfig/opts" |
| ... | ... |
@@ -64,19 +65,19 @@ type dispatchRequest struct {
|
| 64 | 64 |
attributes map[string]bool |
| 65 | 65 |
flags *BFlags |
| 66 | 66 |
original string |
| 67 |
- runConfig *container.Config |
|
| 68 | 67 |
shlex *ShellLex |
| 68 |
+ state *dispatchState |
|
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 |
-func newDispatchRequestFromNode(node *parser.Node, builder *Builder, args []string, shlex *ShellLex) dispatchRequest {
|
|
| 71 |
+func newDispatchRequestFromOptions(options dispatchOptions, builder *Builder, args []string) dispatchRequest {
|
|
| 72 | 72 |
return dispatchRequest{
|
| 73 | 73 |
builder: builder, |
| 74 | 74 |
args: args, |
| 75 |
- attributes: node.Attributes, |
|
| 76 |
- original: node.Original, |
|
| 77 |
- flags: NewBFlagsWithArgs(node.Flags), |
|
| 78 |
- runConfig: builder.runConfig, |
|
| 79 |
- shlex: shlex, |
|
| 75 |
+ attributes: options.node.Attributes, |
|
| 76 |
+ original: options.node.Original, |
|
| 77 |
+ flags: NewBFlagsWithArgs(options.node.Flags), |
|
| 78 |
+ shlex: options.shlex, |
|
| 79 |
+ state: options.state, |
|
| 80 | 80 |
} |
| 81 | 81 |
} |
| 82 | 82 |
|
| ... | ... |
@@ -107,6 +108,10 @@ func init() {
|
| 107 | 107 |
} |
| 108 | 108 |
} |
| 109 | 109 |
|
| 110 |
+func formatStep(stepN int, stepTotal int) string {
|
|
| 111 |
+ return fmt.Sprintf("%d/%d", stepN+1, stepTotal)
|
|
| 112 |
+} |
|
| 113 |
+ |
|
| 110 | 114 |
// This method is the entrypoint to all statement handling routines. |
| 111 | 115 |
// |
| 112 | 116 |
// Almost all nodes will have this structure: |
| ... | ... |
@@ -121,117 +126,144 @@ func init() {
|
| 121 | 121 |
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to |
| 122 | 122 |
// deal with that, at least until it becomes more of a general concern with new |
| 123 | 123 |
// features. |
| 124 |
-func (b *Builder) dispatch(stepN int, stepTotal int, node *parser.Node, shlex *ShellLex) error {
|
|
| 124 |
+func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
|
|
| 125 |
+ node := options.node |
|
| 125 | 126 |
cmd := node.Value |
| 126 | 127 |
upperCasedCmd := strings.ToUpper(cmd) |
| 127 | 128 |
|
| 128 | 129 |
// To ensure the user is given a decent error message if the platform |
| 129 | 130 |
// on which the daemon is running does not support a builder command. |
| 130 | 131 |
if err := platformSupports(strings.ToLower(cmd)); err != nil {
|
| 131 |
- return err |
|
| 132 |
+ return nil, err |
|
| 132 | 133 |
} |
| 133 | 134 |
|
| 134 |
- strList := []string{}
|
|
| 135 |
- msg := bytes.NewBufferString(fmt.Sprintf("Step %d/%d : %s", stepN+1, stepTotal, upperCasedCmd))
|
|
| 136 |
- |
|
| 137 |
- if len(node.Flags) > 0 {
|
|
| 138 |
- msg.WriteString(strings.Join(node.Flags, " ")) |
|
| 139 |
- } |
|
| 135 |
+ msg := bytes.NewBufferString(fmt.Sprintf("Step %s : %s%s",
|
|
| 136 |
+ options.stepMsg, upperCasedCmd, formatFlags(node.Flags))) |
|
| 140 | 137 |
|
| 138 |
+ args := []string{}
|
|
| 141 | 139 |
ast := node |
| 142 |
- if cmd == "onbuild" {
|
|
| 143 |
- if ast.Next == nil {
|
|
| 144 |
- return errors.New("ONBUILD requires at least one argument")
|
|
| 140 |
+ if cmd == command.Onbuild {
|
|
| 141 |
+ var err error |
|
| 142 |
+ ast, args, err = handleOnBuildNode(node, msg) |
|
| 143 |
+ if err != nil {
|
|
| 144 |
+ return nil, err |
|
| 145 | 145 |
} |
| 146 |
- ast = ast.Next.Children[0] |
|
| 147 |
- strList = append(strList, ast.Value) |
|
| 148 |
- msg.WriteString(" " + ast.Value)
|
|
| 146 |
+ } |
|
| 149 | 147 |
|
| 150 |
- if len(ast.Flags) > 0 {
|
|
| 151 |
- msg.WriteString(" " + strings.Join(ast.Flags, " "))
|
|
| 152 |
- } |
|
| 148 |
+ runConfigEnv := options.state.runConfig.Env |
|
| 149 |
+ envs := append(runConfigEnv, b.buildArgs.FilterAllowed(runConfigEnv)...) |
|
| 150 |
+ processFunc := createProcessWordFunc(options.shlex, cmd, envs) |
|
| 151 |
+ words, err := getDispatchArgsFromNode(ast, processFunc, msg) |
|
| 152 |
+ if err != nil {
|
|
| 153 |
+ return nil, err |
|
| 153 | 154 |
} |
| 155 |
+ args = append(args, words...) |
|
| 154 | 156 |
|
| 155 |
- msgList := initMsgList(ast) |
|
| 156 |
- // Append build args to runConfig environment variables |
|
| 157 |
- envs := append(b.runConfig.Env, b.buildArgsWithoutConfigEnv()...) |
|
| 157 |
+ fmt.Fprintln(b.Stdout, msg.String()) |
|
| 158 | 158 |
|
| 159 |
- processFunc := getProcessFunc(shlex, cmd) |
|
| 160 |
- for i := 0; ast.Next != nil; i++ {
|
|
| 161 |
- ast = ast.Next |
|
| 162 |
- words, err := processFunc(ast.Value, envs) |
|
| 163 |
- if err != nil {
|
|
| 164 |
- return err |
|
| 165 |
- } |
|
| 166 |
- strList = append(strList, words...) |
|
| 167 |
- msgList[i] = ast.Value |
|
| 159 |
+ f, ok := evaluateTable[cmd] |
|
| 160 |
+ if !ok {
|
|
| 161 |
+ return nil, fmt.Errorf("unknown instruction: %s", upperCasedCmd)
|
|
| 168 | 162 |
} |
| 163 |
+ if err := f(newDispatchRequestFromOptions(options, b, args)); err != nil {
|
|
| 164 |
+ return nil, err |
|
| 165 |
+ } |
|
| 166 |
+ options.state.updateRunConfig() |
|
| 167 |
+ return options.state, nil |
|
| 168 |
+} |
|
| 169 | 169 |
|
| 170 |
- msg.WriteString(" " + strings.Join(msgList, " "))
|
|
| 171 |
- fmt.Fprintln(b.Stdout, msg.String()) |
|
| 170 |
+type dispatchOptions struct {
|
|
| 171 |
+ state *dispatchState |
|
| 172 |
+ stepMsg string |
|
| 173 |
+ node *parser.Node |
|
| 174 |
+ shlex *ShellLex |
|
| 175 |
+} |
|
| 172 | 176 |
|
| 173 |
- // XXX yes, we skip any cmds that are not valid; the parser should have |
|
| 174 |
- // picked these out already. |
|
| 175 |
- if f, ok := evaluateTable[cmd]; ok {
|
|
| 176 |
- if err := f(newDispatchRequestFromNode(node, b, strList, shlex)); err != nil {
|
|
| 177 |
- return err |
|
| 178 |
- } |
|
| 179 |
- // TODO: return an object instead of setting things on builder |
|
| 180 |
- // If the step created a new image set it as the imageID for the |
|
| 181 |
- // current runConfig |
|
| 182 |
- b.runConfig.Image = b.image |
|
| 183 |
- return nil |
|
| 177 |
+// dispatchState is a data object which is modified by dispatchers |
|
| 178 |
+type dispatchState struct {
|
|
| 179 |
+ runConfig *container.Config |
|
| 180 |
+ maintainer string |
|
| 181 |
+ cmdSet bool |
|
| 182 |
+ noBaseImage bool |
|
| 183 |
+ imageID string |
|
| 184 |
+ baseImage builder.Image |
|
| 185 |
+ stageName string |
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+func newDispatchState() *dispatchState {
|
|
| 189 |
+ return &dispatchState{runConfig: &container.Config{}}
|
|
| 190 |
+} |
|
| 191 |
+ |
|
| 192 |
+func (r *dispatchState) updateRunConfig() {
|
|
| 193 |
+ r.runConfig.Image = r.imageID |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+// hasFromImage returns true if the builder has processed a `FROM <image>` line |
|
| 197 |
+func (r *dispatchState) hasFromImage() bool {
|
|
| 198 |
+ return r.imageID != "" || r.noBaseImage |
|
| 199 |
+} |
|
| 200 |
+ |
|
| 201 |
+func (r *dispatchState) runConfigEnvMapping() map[string]string {
|
|
| 202 |
+ return opts.ConvertKVStringsToMap(r.runConfig.Env) |
|
| 203 |
+} |
|
| 204 |
+ |
|
| 205 |
+func (r *dispatchState) isCurrentStage(target string) bool {
|
|
| 206 |
+ if target == "" {
|
|
| 207 |
+ return false |
|
| 184 | 208 |
} |
| 209 |
+ return strings.EqualFold(r.stageName, target) |
|
| 210 |
+} |
|
| 185 | 211 |
|
| 186 |
- return fmt.Errorf("Unknown instruction: %s", upperCasedCmd)
|
|
| 212 |
+func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
|
|
| 213 |
+ if ast.Next == nil {
|
|
| 214 |
+ return nil, nil, errors.New("ONBUILD requires at least one argument")
|
|
| 215 |
+ } |
|
| 216 |
+ ast = ast.Next.Children[0] |
|
| 217 |
+ msg.WriteString(" " + ast.Value + formatFlags(ast.Flags))
|
|
| 218 |
+ return ast, []string{ast.Value}, nil
|
|
| 187 | 219 |
} |
| 188 | 220 |
|
| 189 |
-// count the number of nodes that we are going to traverse first |
|
| 190 |
-// allocation of those list a lot when they have a lot of arguments |
|
| 191 |
-func initMsgList(cursor *parser.Node) []string {
|
|
| 192 |
- var n int |
|
| 193 |
- for ; cursor.Next != nil; n++ {
|
|
| 194 |
- cursor = cursor.Next |
|
| 221 |
+func formatFlags(flags []string) string {
|
|
| 222 |
+ if len(flags) > 0 {
|
|
| 223 |
+ return " " + strings.Join(flags, " ") |
|
| 195 | 224 |
} |
| 196 |
- return make([]string, n) |
|
| 225 |
+ return "" |
|
| 197 | 226 |
} |
| 198 | 227 |
|
| 199 |
-type processFunc func(string, []string) ([]string, error) |
|
| 228 |
+func getDispatchArgsFromNode(ast *parser.Node, processFunc processWordFunc, msg *bytes.Buffer) ([]string, error) {
|
|
| 229 |
+ args := []string{}
|
|
| 230 |
+ for i := 0; ast.Next != nil; i++ {
|
|
| 231 |
+ ast = ast.Next |
|
| 232 |
+ words, err := processFunc(ast.Value) |
|
| 233 |
+ if err != nil {
|
|
| 234 |
+ return nil, err |
|
| 235 |
+ } |
|
| 236 |
+ args = append(args, words...) |
|
| 237 |
+ msg.WriteString(" " + ast.Value)
|
|
| 238 |
+ } |
|
| 239 |
+ return args, nil |
|
| 240 |
+} |
|
| 200 | 241 |
|
| 201 |
-func getProcessFunc(shlex *ShellLex, cmd string) processFunc {
|
|
| 242 |
+type processWordFunc func(string) ([]string, error) |
|
| 243 |
+ |
|
| 244 |
+func createProcessWordFunc(shlex *ShellLex, cmd string, envs []string) processWordFunc {
|
|
| 202 | 245 |
switch {
|
| 203 | 246 |
case !replaceEnvAllowed[cmd]: |
| 204 |
- return func(word string, _ []string) ([]string, error) {
|
|
| 247 |
+ return func(word string) ([]string, error) {
|
|
| 205 | 248 |
return []string{word}, nil
|
| 206 | 249 |
} |
| 207 | 250 |
case allowWordExpansion[cmd]: |
| 208 |
- return shlex.ProcessWords |
|
| 251 |
+ return func(word string) ([]string, error) {
|
|
| 252 |
+ return shlex.ProcessWords(word, envs) |
|
| 253 |
+ } |
|
| 209 | 254 |
default: |
| 210 |
- return func(word string, envs []string) ([]string, error) {
|
|
| 255 |
+ return func(word string) ([]string, error) {
|
|
| 211 | 256 |
word, err := shlex.ProcessWord(word, envs) |
| 212 | 257 |
return []string{word}, err
|
| 213 | 258 |
} |
| 214 | 259 |
} |
| 215 | 260 |
} |
| 216 | 261 |
|
| 217 |
-// buildArgsWithoutConfigEnv returns a list of key=value pairs for all the build |
|
| 218 |
-// args that are not overriden by runConfig environment variables. |
|
| 219 |
-func (b *Builder) buildArgsWithoutConfigEnv() []string {
|
|
| 220 |
- envs := []string{}
|
|
| 221 |
- configEnv := b.runConfigEnvMapping() |
|
| 222 |
- |
|
| 223 |
- for key, val := range b.buildArgs.GetAllAllowed() {
|
|
| 224 |
- if _, ok := configEnv[key]; !ok {
|
|
| 225 |
- envs = append(envs, fmt.Sprintf("%s=%s", key, val))
|
|
| 226 |
- } |
|
| 227 |
- } |
|
| 228 |
- return envs |
|
| 229 |
-} |
|
| 230 |
- |
|
| 231 |
-func (b *Builder) runConfigEnvMapping() map[string]string {
|
|
| 232 |
- return opts.ConvertKVStringsToMap(b.runConfig.Env) |
|
| 233 |
-} |
|
| 234 |
- |
|
| 235 | 262 |
// checkDispatch does a simple check for syntax errors of the Dockerfile. |
| 236 | 263 |
// Because some of the instructions can only be validated through runtime, |
| 237 | 264 |
// arg, env, etc., this syntax check will not be complete and could not replace |
| ... | ... |
@@ -123,7 +123,7 @@ func initDispatchTestCases() []dispatchTestCase {
|
| 123 | 123 |
{
|
| 124 | 124 |
name: "Invalid instruction", |
| 125 | 125 |
dockerfile: `foo bar`, |
| 126 |
- expectedError: "Unknown instruction: FOO", |
|
| 126 |
+ expectedError: "unknown instruction: FOO", |
|
| 127 | 127 |
files: nil, |
| 128 | 128 |
}} |
| 129 | 129 |
|
| ... | ... |
@@ -177,13 +177,11 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
|
| 177 | 177 |
t.Fatalf("Error when parsing Dockerfile: %s", err)
|
| 178 | 178 |
} |
| 179 | 179 |
|
| 180 |
- config := &container.Config{}
|
|
| 181 | 180 |
options := &types.ImageBuildOptions{
|
| 182 | 181 |
BuildArgs: make(map[string]*string), |
| 183 | 182 |
} |
| 184 | 183 |
|
| 185 | 184 |
b := &Builder{
|
| 186 |
- runConfig: config, |
|
| 187 | 185 |
options: options, |
| 188 | 186 |
Stdout: ioutil.Discard, |
| 189 | 187 |
source: context, |
| ... | ... |
@@ -192,7 +190,14 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
|
| 192 | 192 |
|
| 193 | 193 |
shlex := NewShellLex(parser.DefaultEscapeToken) |
| 194 | 194 |
n := result.AST |
| 195 |
- err = b.dispatch(0, len(n.Children), n.Children[0], shlex) |
|
| 195 |
+ state := &dispatchState{runConfig: &container.Config{}}
|
|
| 196 |
+ opts := dispatchOptions{
|
|
| 197 |
+ state: state, |
|
| 198 |
+ stepMsg: formatStep(0, len(n.Children)), |
|
| 199 |
+ node: n.Children[0], |
|
| 200 |
+ shlex: shlex, |
|
| 201 |
+ } |
|
| 202 |
+ state, err = b.dispatch(opts) |
|
| 196 | 203 |
|
| 197 | 204 |
if err == nil {
|
| 198 | 205 |
t.Fatalf("No error when executing test %s", testCase.name)
|
| ... | ... |
@@ -19,11 +19,10 @@ type pathCache interface {
|
| 19 | 19 |
// imageContexts is a helper for stacking up built image rootfs and reusing |
| 20 | 20 |
// them as contexts |
| 21 | 21 |
type imageContexts struct {
|
| 22 |
- b *Builder |
|
| 23 |
- list []*imageMount |
|
| 24 |
- byName map[string]*imageMount |
|
| 25 |
- cache pathCache |
|
| 26 |
- currentName string |
|
| 22 |
+ b *Builder |
|
| 23 |
+ list []*imageMount |
|
| 24 |
+ byName map[string]*imageMount |
|
| 25 |
+ cache pathCache |
|
| 27 | 26 |
} |
| 28 | 27 |
|
| 29 | 28 |
func (ic *imageContexts) newImageMount(id string) *imageMount {
|
| ... | ... |
@@ -41,7 +40,6 @@ func (ic *imageContexts) add(name string) (*imageMount, error) {
|
| 41 | 41 |
} |
| 42 | 42 |
ic.byName[name] = im |
| 43 | 43 |
} |
| 44 |
- ic.currentName = name |
|
| 45 | 44 |
ic.list = append(ic.list, im) |
| 46 | 45 |
return im, nil |
| 47 | 46 |
} |
| ... | ... |
@@ -96,13 +94,6 @@ func (ic *imageContexts) unmount() (retErr error) {
|
| 96 | 96 |
return |
| 97 | 97 |
} |
| 98 | 98 |
|
| 99 |
-func (ic *imageContexts) isCurrentTarget(target string) bool {
|
|
| 100 |
- if target == "" {
|
|
| 101 |
- return false |
|
| 102 |
- } |
|
| 103 |
- return strings.EqualFold(ic.currentName, target) |
|
| 104 |
-} |
|
| 105 |
- |
|
| 106 | 99 |
func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
|
| 107 | 100 |
if ic.cache != nil {
|
| 108 | 101 |
if id == "" {
|
| ... | ... |
@@ -35,16 +35,16 @@ import ( |
| 35 | 35 |
"github.com/pkg/errors" |
| 36 | 36 |
) |
| 37 | 37 |
|
| 38 |
-func (b *Builder) commit(comment string) error {
|
|
| 38 |
+func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
|
|
| 39 | 39 |
if b.disableCommit {
|
| 40 | 40 |
return nil |
| 41 | 41 |
} |
| 42 |
- if !b.hasFromImage() {
|
|
| 42 |
+ if !dispatchState.hasFromImage() {
|
|
| 43 | 43 |
return errors.New("Please provide a source image with `from` prior to commit")
|
| 44 | 44 |
} |
| 45 | 45 |
|
| 46 |
- runConfigWithCommentCmd := copyRunConfig(b.runConfig, withCmdComment(comment)) |
|
| 47 |
- hit, err := b.probeCache(b.image, runConfigWithCommentCmd) |
|
| 46 |
+ runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment)) |
|
| 47 |
+ hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd) |
|
| 48 | 48 |
if err != nil || hit {
|
| 49 | 49 |
return err |
| 50 | 50 |
} |
| ... | ... |
@@ -53,20 +53,21 @@ func (b *Builder) commit(comment string) error {
|
| 53 | 53 |
return err |
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 |
- return b.commitContainer(id, runConfigWithCommentCmd) |
|
| 56 |
+ return b.commitContainer(dispatchState, id, runConfigWithCommentCmd) |
|
| 57 | 57 |
} |
| 58 | 58 |
|
| 59 |
-func (b *Builder) commitContainer(id string, containerConfig *container.Config) error {
|
|
| 59 |
+// TODO: see if any args can be dropped |
|
| 60 |
+func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
|
|
| 60 | 61 |
if b.disableCommit {
|
| 61 | 62 |
return nil |
| 62 | 63 |
} |
| 63 | 64 |
|
| 64 | 65 |
commitCfg := &backend.ContainerCommitConfig{
|
| 65 | 66 |
ContainerCommitConfig: types.ContainerCommitConfig{
|
| 66 |
- Author: b.maintainer, |
|
| 67 |
+ Author: dispatchState.maintainer, |
|
| 67 | 68 |
Pause: true, |
| 68 | 69 |
// TODO: this should be done by Commit() |
| 69 |
- Config: copyRunConfig(b.runConfig), |
|
| 70 |
+ Config: copyRunConfig(dispatchState.runConfig), |
|
| 70 | 71 |
}, |
| 71 | 72 |
ContainerConfig: containerConfig, |
| 72 | 73 |
} |
| ... | ... |
@@ -77,10 +78,8 @@ func (b *Builder) commitContainer(id string, containerConfig *container.Config) |
| 77 | 77 |
return err |
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 |
- // TODO: this function should return imageID and runConfig instead of setting |
|
| 81 |
- // then on the builder |
|
| 82 |
- b.image = imageID |
|
| 83 |
- b.imageContexts.update(imageID, b.runConfig) |
|
| 80 |
+ dispatchState.imageID = imageID |
|
| 81 |
+ b.imageContexts.update(imageID, dispatchState.runConfig) |
|
| 84 | 82 |
return nil |
| 85 | 83 |
} |
| 86 | 84 |
|
| ... | ... |
@@ -91,7 +90,9 @@ type copyInfo struct {
|
| 91 | 91 |
decompress bool |
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
-func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error {
|
|
| 94 |
+// TODO: this needs to be split so that a Builder method doesn't accept req |
|
| 95 |
+func (b *Builder) runContextCommand(req dispatchRequest, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error {
|
|
| 96 |
+ args := req.args |
|
| 95 | 97 |
if len(args) < 2 {
|
| 96 | 98 |
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
| 97 | 99 |
} |
| ... | ... |
@@ -163,9 +164,9 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 163 | 163 |
|
| 164 | 164 |
// TODO: should this have been using origPaths instead of srcHash in the comment? |
| 165 | 165 |
runConfigWithCommentCmd := copyRunConfig( |
| 166 |
- b.runConfig, |
|
| 166 |
+ req.state.runConfig, |
|
| 167 | 167 |
withCmdCommentString(fmt.Sprintf("%s %s in %s ", cmdName, srcHash, dest)))
|
| 168 |
- if hit, err := b.probeCache(b.image, runConfigWithCommentCmd); err != nil || hit {
|
|
| 168 |
+ if hit, err := b.probeCache(req.state, runConfigWithCommentCmd); err != nil || hit {
|
|
| 169 | 169 |
return err |
| 170 | 170 |
} |
| 171 | 171 |
|
| ... | ... |
@@ -181,7 +182,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 181 | 181 |
|
| 182 | 182 |
// Twiddle the destination when it's a relative path - meaning, make it |
| 183 | 183 |
// relative to the WORKINGDIR |
| 184 |
- if dest, err = normaliseDest(cmdName, b.runConfig.WorkingDir, dest); err != nil {
|
|
| 184 |
+ if dest, err = normaliseDest(cmdName, req.state.runConfig.WorkingDir, dest); err != nil {
|
|
| 185 | 185 |
return err |
| 186 | 186 |
} |
| 187 | 187 |
|
| ... | ... |
@@ -191,7 +192,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 191 | 191 |
} |
| 192 | 192 |
} |
| 193 | 193 |
|
| 194 |
- return b.commitContainer(container.ID, runConfigWithCommentCmd) |
|
| 194 |
+ return b.commitContainer(req.state, container.ID, runConfigWithCommentCmd) |
|
| 195 | 195 |
} |
| 196 | 196 |
|
| 197 | 197 |
type runConfigModifier func(*container.Config) |
| ... | ... |
@@ -479,20 +480,20 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression |
| 479 | 479 |
return copyInfos, nil |
| 480 | 480 |
} |
| 481 | 481 |
|
| 482 |
-func (b *Builder) processImageFrom(img builder.Image) error {
|
|
| 482 |
+func (b *Builder) processImageFrom(dispatchState *dispatchState, img builder.Image) error {
|
|
| 483 | 483 |
if img != nil {
|
| 484 |
- b.image = img.ImageID() |
|
| 484 |
+ dispatchState.imageID = img.ImageID() |
|
| 485 | 485 |
|
| 486 | 486 |
if img.RunConfig() != nil {
|
| 487 |
- b.runConfig = img.RunConfig() |
|
| 487 |
+ dispatchState.runConfig = img.RunConfig() |
|
| 488 | 488 |
} |
| 489 | 489 |
} |
| 490 | 490 |
|
| 491 | 491 |
// Check to see if we have a default PATH, note that windows won't |
| 492 | 492 |
// have one as it's set by HCS |
| 493 | 493 |
if system.DefaultPathEnv != "" {
|
| 494 |
- if _, ok := b.runConfigEnvMapping()["PATH"]; !ok {
|
|
| 495 |
- b.runConfig.Env = append(b.runConfig.Env, |
|
| 494 |
+ if _, ok := dispatchState.runConfigEnvMapping()["PATH"]; !ok {
|
|
| 495 |
+ dispatchState.runConfig.Env = append(dispatchState.runConfig.Env, |
|
| 496 | 496 |
"PATH="+system.DefaultPathEnv) |
| 497 | 497 |
} |
| 498 | 498 |
} |
| ... | ... |
@@ -503,7 +504,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
|
| 503 | 503 |
} |
| 504 | 504 |
|
| 505 | 505 |
// Process ONBUILD triggers if they exist |
| 506 |
- if nTriggers := len(b.runConfig.OnBuild); nTriggers != 0 {
|
|
| 506 |
+ if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
|
|
| 507 | 507 |
word := "trigger" |
| 508 | 508 |
if nTriggers > 1 {
|
| 509 | 509 |
word = "triggers" |
| ... | ... |
@@ -512,21 +513,21 @@ func (b *Builder) processImageFrom(img builder.Image) error {
|
| 512 | 512 |
} |
| 513 | 513 |
|
| 514 | 514 |
// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. |
| 515 |
- onBuildTriggers := b.runConfig.OnBuild |
|
| 516 |
- b.runConfig.OnBuild = []string{}
|
|
| 515 |
+ onBuildTriggers := dispatchState.runConfig.OnBuild |
|
| 516 |
+ dispatchState.runConfig.OnBuild = []string{}
|
|
| 517 | 517 |
|
| 518 | 518 |
// Reset stdin settings as all build actions run without stdin |
| 519 |
- b.runConfig.OpenStdin = false |
|
| 520 |
- b.runConfig.StdinOnce = false |
|
| 519 |
+ dispatchState.runConfig.OpenStdin = false |
|
| 520 |
+ dispatchState.runConfig.StdinOnce = false |
|
| 521 | 521 |
|
| 522 | 522 |
// parse the ONBUILD triggers by invoking the parser |
| 523 | 523 |
for _, step := range onBuildTriggers {
|
| 524 |
- result, err := parser.Parse(strings.NewReader(step)) |
|
| 524 |
+ dockerfile, err := parser.Parse(strings.NewReader(step)) |
|
| 525 | 525 |
if err != nil {
|
| 526 | 526 |
return err |
| 527 | 527 |
} |
| 528 | 528 |
|
| 529 |
- for _, n := range result.AST.Children {
|
|
| 529 |
+ for _, n := range dockerfile.AST.Children {
|
|
| 530 | 530 |
if err := checkDispatch(n); err != nil {
|
| 531 | 531 |
return err |
| 532 | 532 |
} |
| ... | ... |
@@ -540,7 +541,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
|
| 540 | 540 |
} |
| 541 | 541 |
} |
| 542 | 542 |
|
| 543 |
- if err := dispatchFromDockerfile(b, result); err != nil {
|
|
| 543 |
+ if _, err := dispatchFromDockerfile(b, dockerfile, dispatchState); err != nil {
|
|
| 544 | 544 |
return err |
| 545 | 545 |
} |
| 546 | 546 |
} |
| ... | ... |
@@ -551,12 +552,12 @@ func (b *Builder) processImageFrom(img builder.Image) error {
|
| 551 | 551 |
// If an image is found, probeCache returns `(true, nil)`. |
| 552 | 552 |
// If no image is found, it returns `(false, nil)`. |
| 553 | 553 |
// If there is any error, it returns `(false, err)`. |
| 554 |
-func (b *Builder) probeCache(parentID string, runConfig *container.Config) (bool, error) {
|
|
| 554 |
+func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
|
|
| 555 | 555 |
c := b.imageCache |
| 556 | 556 |
if c == nil || b.options.NoCache || b.cacheBusted {
|
| 557 | 557 |
return false, nil |
| 558 | 558 |
} |
| 559 |
- cache, err := c.GetCache(parentID, runConfig) |
|
| 559 |
+ cache, err := c.GetCache(dispatchState.imageID, runConfig) |
|
| 560 | 560 |
if err != nil {
|
| 561 | 561 |
return false, err |
| 562 | 562 |
} |
| ... | ... |
@@ -568,16 +569,13 @@ func (b *Builder) probeCache(parentID string, runConfig *container.Config) (bool |
| 568 | 568 |
|
| 569 | 569 |
fmt.Fprint(b.Stdout, " ---> Using cache\n") |
| 570 | 570 |
logrus.Debugf("[BUILDER] Use cached version: %s", runConfig.Cmd)
|
| 571 |
- b.image = string(cache) |
|
| 572 |
- b.imageContexts.update(b.image, runConfig) |
|
| 571 |
+ dispatchState.imageID = string(cache) |
|
| 572 |
+ b.imageContexts.update(dispatchState.imageID, runConfig) |
|
| 573 | 573 |
|
| 574 | 574 |
return true, nil |
| 575 | 575 |
} |
| 576 | 576 |
|
| 577 | 577 |
func (b *Builder) create(runConfig *container.Config) (string, error) {
|
| 578 |
- if !b.hasFromImage() {
|
|
| 579 |
- return "", errors.New("Please provide a source image with `from` prior to run")
|
|
| 580 |
- } |
|
| 581 | 578 |
resources := container.Resources{
|
| 582 | 579 |
CgroupParent: b.options.CgroupParent, |
| 583 | 580 |
CPUShares: b.options.CPUShares, |