github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/builder/dockerfile/dbuilder.go (about) 1 package dockerfile 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "strings" 11 12 "github.com/Sirupsen/logrus" 13 apierrors "github.com/docker/docker/api/errors" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/backend" 16 "github.com/docker/docker/api/types/container" 17 "github.com/docker/docker/builder" 18 "github.com/docker/docker/builder/dockerfile/parser" 19 "github.com/docker/docker/image" 20 "github.com/docker/docker/pkg/stringid" 21 "github.com/docker/docker/reference" 22 perrors "github.com/pkg/errors" 23 "golang.org/x/net/context" 24 ) 25 26 var validCommitCommands = map[string]bool{ 27 "cmd": true, 28 "entrypoint": true, 29 "healthcheck": true, 30 "env": true, 31 "expose": true, 32 "label": true, 33 "onbuild": true, 34 "user": true, 35 "volume": true, 36 "workdir": true, 37 } 38 39 // BuiltinAllowedBuildArgs is list of built-in allowed build args 40 var BuiltinAllowedBuildArgs = map[string]bool{ 41 "HTTP_PROXY": true, 42 "http_proxy": true, 43 "HTTPS_PROXY": true, 44 "https_proxy": true, 45 "FTP_PROXY": true, 46 "ftp_proxy": true, 47 "NO_PROXY": true, 48 "no_proxy": true, 49 } 50 51 // Builder is a Dockerfile builder 52 // It implements the builder.Backend interface. 53 type Builder struct { 54 options *types.ImageBuildOptions 55 56 Stdout io.Writer 57 Stderr io.Writer 58 Output io.Writer 59 60 docker builder.Backend 61 context builder.Context 62 clientCtx context.Context 63 cancel context.CancelFunc 64 65 dockerfile *parser.Node 66 runConfig *container.Config // runconfig for cmd, run, entrypoint etc. 67 flags *BFlags 68 tmpContainers map[string]struct{} 69 image string // imageID 70 noBaseImage bool 71 maintainer string 72 cmdSet bool 73 disableCommit bool 74 cacheBusted bool 75 allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'. 76 directive parser.Directive 77 78 // TODO: remove once docker.Commit can receive a tag 79 id string 80 81 imageCache builder.ImageCache 82 from builder.Image 83 } 84 85 // BuildManager implements builder.Backend and is shared across all Builder objects. 86 type BuildManager struct { 87 backend builder.Backend 88 } 89 90 // NewBuildManager creates a BuildManager. 91 func NewBuildManager(b builder.Backend) (bm *BuildManager) { 92 return &BuildManager{backend: b} 93 } 94 95 // BuildFromContext builds a new image from a given context. 96 func (bm *BuildManager) BuildFromContext(ctx context.Context, src io.ReadCloser, remote string, buildOptions *types.ImageBuildOptions, pg backend.ProgressWriter) (string, error) { 97 if buildOptions.Squash && !bm.backend.HasExperimental() { 98 return "", apierrors.NewBadRequestError(errors.New("squash is only supported with experimental mode")) 99 } 100 buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(src, remote, pg.ProgressReaderFunc) 101 if err != nil { 102 return "", err 103 } 104 defer func() { 105 if err := buildContext.Close(); err != nil { 106 logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) 107 } 108 }() 109 110 if len(dockerfileName) > 0 { 111 buildOptions.Dockerfile = dockerfileName 112 } 113 b, err := NewBuilder(ctx, buildOptions, bm.backend, builder.DockerIgnoreContext{ModifiableContext: buildContext}, nil) 114 if err != nil { 115 return "", err 116 } 117 return b.build(pg.StdoutFormatter, pg.StderrFormatter, pg.Output) 118 } 119 120 // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config. 121 // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName, 122 // will be read from the Context passed to Build(). 123 func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) { 124 if config == nil { 125 config = new(types.ImageBuildOptions) 126 } 127 if config.BuildArgs == nil { 128 config.BuildArgs = make(map[string]*string) 129 } 130 ctx, cancel := context.WithCancel(clientCtx) 131 b = &Builder{ 132 clientCtx: ctx, 133 cancel: cancel, 134 options: config, 135 Stdout: os.Stdout, 136 Stderr: os.Stderr, 137 docker: backend, 138 context: buildContext, 139 runConfig: new(container.Config), 140 tmpContainers: map[string]struct{}{}, 141 id: stringid.GenerateNonCryptoID(), 142 allowedBuildArgs: make(map[string]bool), 143 directive: parser.Directive{ 144 EscapeSeen: false, 145 LookingForDirectives: true, 146 }, 147 } 148 if icb, ok := backend.(builder.ImageCacheBuilder); ok { 149 b.imageCache = icb.MakeImageCache(config.CacheFrom) 150 } 151 152 parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape 153 154 if dockerfile != nil { 155 b.dockerfile, err = parser.Parse(dockerfile, &b.directive) 156 if err != nil { 157 return nil, err 158 } 159 } 160 161 return b, nil 162 } 163 164 // sanitizeRepoAndTags parses the raw "t" parameter received from the client 165 // to a slice of repoAndTag. 166 // It also validates each repoName and tag. 167 func sanitizeRepoAndTags(names []string) ([]reference.Named, error) { 168 var ( 169 repoAndTags []reference.Named 170 // This map is used for deduplicating the "-t" parameter. 171 uniqNames = make(map[string]struct{}) 172 ) 173 for _, repo := range names { 174 if repo == "" { 175 continue 176 } 177 178 ref, err := reference.ParseNamed(repo) 179 if err != nil { 180 return nil, err 181 } 182 183 ref = reference.WithDefaultTag(ref) 184 185 if _, isCanonical := ref.(reference.Canonical); isCanonical { 186 return nil, errors.New("build tag cannot contain a digest") 187 } 188 189 if _, isTagged := ref.(reference.NamedTagged); !isTagged { 190 ref, err = reference.WithTag(ref, reference.DefaultTag) 191 if err != nil { 192 return nil, err 193 } 194 } 195 196 nameWithTag := ref.String() 197 198 if _, exists := uniqNames[nameWithTag]; !exists { 199 uniqNames[nameWithTag] = struct{}{} 200 repoAndTags = append(repoAndTags, ref) 201 } 202 } 203 return repoAndTags, nil 204 } 205 206 // build runs the Dockerfile builder from a context and a docker object that allows to make calls 207 // to Docker. 208 // 209 // This will (barring errors): 210 // 211 // * read the dockerfile from context 212 // * parse the dockerfile if not already parsed 213 // * walk the AST and execute it by dispatching to handlers. If Remove 214 // or ForceRemove is set, additional cleanup around containers happens after 215 // processing. 216 // * Tag image, if applicable. 217 // * Print a happy message and return the image ID. 218 // 219 func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) { 220 b.Stdout = stdout 221 b.Stderr = stderr 222 b.Output = out 223 224 fmt.Println("dockerfile/builder.go build()") 225 226 // If Dockerfile was not parsed yet, extract it from the Context 227 if b.dockerfile == nil { 228 if err := b.readDockerfile(); err != nil { 229 return "", err 230 } 231 } 232 233 repoAndTags, err := sanitizeRepoAndTags(b.options.Tags) 234 if err != nil { 235 return "", err 236 } 237 238 if len(b.options.Labels) > 0 { 239 line := "LABEL " 240 for k, v := range b.options.Labels { 241 line += fmt.Sprintf("%q='%s' ", k, v) 242 } 243 _, node, err := parser.ParseLine(line, &b.directive, false) 244 if err != nil { 245 return "", err 246 } 247 b.dockerfile.Children = append(b.dockerfile.Children, node) 248 } 249 250 var shortImgID string 251 total := len(b.dockerfile.Children) 252 for _, n := range b.dockerfile.Children { 253 if err := b.checkDispatch(n, false); err != nil { 254 return "", err 255 } 256 } 257 258 //var tmpFirstComtainerID string 259 tmpFirstContainerID := "" 260 for i, n := range b.dockerfile.Children { 261 fmt.Println("dockerfile/builder.go for()", i) 262 if i >= 1 { 263 // fmt.Println("dockerfile/builder.go build() stop the build function!!!") 264 // break 265 } 266 267 select { 268 case <-b.clientCtx.Done(): 269 logrus.Debug("Builder: build cancelled!") 270 fmt.Fprintf(b.Stdout, "Build cancelled") 271 return "", fmt.Errorf("Build cancelled") 272 default: 273 // Not cancelled yet, keep going... 274 } 275 276 277 /* if id, err := b.dispatch(i, total, n, tmpFirstContainerID); err != nil { 278 if b.options.ForceRemove { 279 // b.clearTmp() 280 fmt.Println("builder.go/build() Don't force remove tmpContainer") 281 } 282 return "", err 283 }else { 284 tmpFirstContainerID = id 285 if id == "" { 286 id = "nullll" 287 } 288 fmt.Println("dockerfile/evaluator.go dispatches() ", id) 289 } 290 */ 291 292 if _, err := b.dispatch(i, total, n, tmpFirstContainerID); err != nil { 293 if b.options.ForceRemove { 294 b.clearTmp() 295 fmt.Println("builder.go/build() Don't force remove tmpContainer") 296 } 297 return "", err 298 }else { 299 // tmpFirstContainerID = id 300 // if id == "" { 301 // id = "nullll" 302 // } 303 // fmt.Println("dockerfile/evaluator.go dispatches() ", id) 304 fmt.Println("dockerfile/evaluator.go dispatches() completely!") 305 } 306 307 shortImgID = stringid.TruncateID(b.image) 308 fmt.Fprintf(b.Stdout, " ---> %s builder.go/build()\n", shortImgID) 309 if b.options.Remove { 310 b.clearTmp() 311 fmt.Println("builder.go/build() Don't remove tmpContainer") 312 } 313 } 314 315 // check if there are any leftover build-args that were passed but not 316 // consumed during build. Return a warning, if there are any. 317 leftoverArgs := []string{} 318 for arg := range b.options.BuildArgs { 319 if !b.isBuildArgAllowed(arg) { 320 leftoverArgs = append(leftoverArgs, arg) 321 } 322 } 323 324 if len(leftoverArgs) > 0 { 325 fmt.Fprintf(b.Stderr, "[Warning] One or more build-args %v were not consumed\n", leftoverArgs) 326 } 327 328 if b.image == "" { 329 return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") 330 } 331 332 if b.options.Squash { 333 var fromID string 334 if b.from != nil { 335 fromID = b.from.ImageID() 336 } 337 b.image, err = b.docker.SquashImage(b.image, fromID) 338 if err != nil { 339 return "", perrors.Wrap(err, "error squashing image") 340 } 341 } 342 343 imageID := image.ID(b.image) 344 for _, rt := range repoAndTags { 345 if err := b.docker.TagImageWithReference(imageID, rt); err != nil { 346 return "", err 347 } 348 } 349 350 fmt.Println("dockerfile/builder.go Successfully built %s", shortImgID) 351 fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID) 352 return b.image, nil 353 } 354 355 // Cancel cancels an ongoing Dockerfile build. 356 func (b *Builder) Cancel() { 357 b.cancel() 358 } 359 360 // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile 361 // It will: 362 // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries. 363 // - Do build by calling builder.dispatch() to call all entries' handling routines 364 // 365 // BuildFromConfig is used by the /commit endpoint, with the changes 366 // coming from the query parameter of the same name. 367 // 368 // TODO: Remove? 369 func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) { 370 fmt.Println("dockerfile/builder.go BuildFromConfig()") 371 b, err := NewBuilder(context.Background(), nil, nil, nil, nil) 372 if err != nil { 373 return nil, err 374 } 375 376 ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), &b.directive) 377 if err != nil { 378 return nil, err 379 } 380 381 // ensure that the commands are valid 382 for _, n := range ast.Children { 383 if !validCommitCommands[n.Value] { 384 return nil, fmt.Errorf("%s is not a valid change command", n.Value) 385 } 386 } 387 388 b.runConfig = config 389 b.Stdout = ioutil.Discard 390 b.Stderr = ioutil.Discard 391 b.disableCommit = true 392 393 total := len(ast.Children) 394 for _, n := range ast.Children { 395 if err := b.checkDispatch(n, false); err != nil { 396 return nil, err 397 } 398 } 399 400 for i, n := range ast.Children { 401 if _, err := b.dispatch(i, total, n, ""); err != nil { 402 return nil, err 403 } 404 } 405 406 return b.runConfig, nil 407 }