| ... | ... |
@@ -17,8 +17,8 @@ import ( |
| 17 | 17 |
"github.com/golang/glog" |
| 18 | 18 |
"golang.org/x/net/context" |
| 19 | 19 |
|
| 20 |
+ "github.com/openshift/imagebuilder/imageprogress" |
|
| 20 | 21 |
starterrors "github.com/openshift/origin/pkg/bootstrap/docker/errors" |
| 21 |
- "github.com/openshift/origin/pkg/util/docker/dockerfile/builder/imageprogress" |
|
| 22 | 22 |
) |
| 23 | 23 |
|
| 24 | 24 |
const openShiftInsecureCIDR = "172.30.0.0/16" |
| ... | ... |
@@ -15,7 +15,7 @@ import ( |
| 15 | 15 |
s2iapi "github.com/openshift/source-to-image/pkg/api" |
| 16 | 16 |
"github.com/openshift/source-to-image/pkg/tar" |
| 17 | 17 |
|
| 18 |
- "github.com/openshift/origin/pkg/util/docker/dockerfile/builder/imageprogress" |
|
| 18 |
+ "github.com/openshift/imagebuilder/imageprogress" |
|
| 19 | 19 |
) |
| 20 | 20 |
|
| 21 | 21 |
var ( |
| 22 | 22 |
deleted file mode 100644 |
| ... | ... |
@@ -1,192 +0,0 @@ |
| 1 |
- |
|
| 2 |
- Apache License |
|
| 3 |
- Version 2.0, January 2004 |
|
| 4 |
- https://www.apache.org/licenses/ |
|
| 5 |
- |
|
| 6 |
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|
| 7 |
- |
|
| 8 |
- 1. Definitions. |
|
| 9 |
- |
|
| 10 |
- "License" shall mean the terms and conditions for use, reproduction, |
|
| 11 |
- and distribution as defined by Sections 1 through 9 of this document. |
|
| 12 |
- |
|
| 13 |
- "Licensor" shall mean the copyright owner or entity authorized by |
|
| 14 |
- the copyright owner that is granting the License. |
|
| 15 |
- |
|
| 16 |
- "Legal Entity" shall mean the union of the acting entity and all |
|
| 17 |
- other entities that control, are controlled by, or are under common |
|
| 18 |
- control with that entity. For the purposes of this definition, |
|
| 19 |
- "control" means (i) the power, direct or indirect, to cause the |
|
| 20 |
- direction or management of such entity, whether by contract or |
|
| 21 |
- otherwise, or (ii) ownership of fifty percent (50%) or more of the |
|
| 22 |
- outstanding shares, or (iii) beneficial ownership of such entity. |
|
| 23 |
- |
|
| 24 |
- "You" (or "Your") shall mean an individual or Legal Entity |
|
| 25 |
- exercising permissions granted by this License. |
|
| 26 |
- |
|
| 27 |
- "Source" form shall mean the preferred form for making modifications, |
|
| 28 |
- including but not limited to software source code, documentation |
|
| 29 |
- source, and configuration files. |
|
| 30 |
- |
|
| 31 |
- "Object" form shall mean any form resulting from mechanical |
|
| 32 |
- transformation or translation of a Source form, including but |
|
| 33 |
- not limited to compiled object code, generated documentation, |
|
| 34 |
- and conversions to other media types. |
|
| 35 |
- |
|
| 36 |
- "Work" shall mean the work of authorship, whether in Source or |
|
| 37 |
- Object form, made available under the License, as indicated by a |
|
| 38 |
- copyright notice that is included in or attached to the work |
|
| 39 |
- (an example is provided in the Appendix below). |
|
| 40 |
- |
|
| 41 |
- "Derivative Works" shall mean any work, whether in Source or Object |
|
| 42 |
- form, that is based on (or derived from) the Work and for which the |
|
| 43 |
- editorial revisions, annotations, elaborations, or other modifications |
|
| 44 |
- represent, as a whole, an original work of authorship. For the purposes |
|
| 45 |
- of this License, Derivative Works shall not include works that remain |
|
| 46 |
- separable from, or merely link (or bind by name) to the interfaces of, |
|
| 47 |
- the Work and Derivative Works thereof. |
|
| 48 |
- |
|
| 49 |
- "Contribution" shall mean any work of authorship, including |
|
| 50 |
- the original version of the Work and any modifications or additions |
|
| 51 |
- to that Work or Derivative Works thereof, that is intentionally |
|
| 52 |
- submitted to Licensor for inclusion in the Work by the copyright owner |
|
| 53 |
- or by an individual or Legal Entity authorized to submit on behalf of |
|
| 54 |
- the copyright owner. For the purposes of this definition, "submitted" |
|
| 55 |
- means any form of electronic, verbal, or written communication sent |
|
| 56 |
- to the Licensor or its representatives, including but not limited to |
|
| 57 |
- communication on electronic mailing lists, source code control systems, |
|
| 58 |
- and issue tracking systems that are managed by, or on behalf of, the |
|
| 59 |
- Licensor for the purpose of discussing and improving the Work, but |
|
| 60 |
- excluding communication that is conspicuously marked or otherwise |
|
| 61 |
- designated in writing by the copyright owner as "Not a Contribution." |
|
| 62 |
- |
|
| 63 |
- "Contributor" shall mean Licensor and any individual or Legal Entity |
|
| 64 |
- on behalf of whom a Contribution has been received by Licensor and |
|
| 65 |
- subsequently incorporated within the Work. |
|
| 66 |
- |
|
| 67 |
- 2. Grant of Copyright License. Subject to the terms and conditions of |
|
| 68 |
- this License, each Contributor hereby grants to You a perpetual, |
|
| 69 |
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 70 |
- copyright license to reproduce, prepare Derivative Works of, |
|
| 71 |
- publicly display, publicly perform, sublicense, and distribute the |
|
| 72 |
- Work and such Derivative Works in Source or Object form. |
|
| 73 |
- |
|
| 74 |
- 3. Grant of Patent License. Subject to the terms and conditions of |
|
| 75 |
- this License, each Contributor hereby grants to You a perpetual, |
|
| 76 |
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 77 |
- (except as stated in this section) patent license to make, have made, |
|
| 78 |
- use, offer to sell, sell, import, and otherwise transfer the Work, |
|
| 79 |
- where such license applies only to those patent claims licensable |
|
| 80 |
- by such Contributor that are necessarily infringed by their |
|
| 81 |
- Contribution(s) alone or by combination of their Contribution(s) |
|
| 82 |
- with the Work to which such Contribution(s) was submitted. If You |
|
| 83 |
- institute patent litigation against any entity (including a |
|
| 84 |
- cross-claim or counterclaim in a lawsuit) alleging that the Work |
|
| 85 |
- or a Contribution incorporated within the Work constitutes direct |
|
| 86 |
- or contributory patent infringement, then any patent licenses |
|
| 87 |
- granted to You under this License for that Work shall terminate |
|
| 88 |
- as of the date such litigation is filed. |
|
| 89 |
- |
|
| 90 |
- 4. Redistribution. You may reproduce and distribute copies of the |
|
| 91 |
- Work or Derivative Works thereof in any medium, with or without |
|
| 92 |
- modifications, and in Source or Object form, provided that You |
|
| 93 |
- meet the following conditions: |
|
| 94 |
- |
|
| 95 |
- (a) You must give any other recipients of the Work or |
|
| 96 |
- Derivative Works a copy of this License; and |
|
| 97 |
- |
|
| 98 |
- (b) You must cause any modified files to carry prominent notices |
|
| 99 |
- stating that You changed the files; and |
|
| 100 |
- |
|
| 101 |
- (c) You must retain, in the Source form of any Derivative Works |
|
| 102 |
- that You distribute, all copyright, patent, trademark, and |
|
| 103 |
- attribution notices from the Source form of the Work, |
|
| 104 |
- excluding those notices that do not pertain to any part of |
|
| 105 |
- the Derivative Works; and |
|
| 106 |
- |
|
| 107 |
- (d) If the Work includes a "NOTICE" text file as part of its |
|
| 108 |
- distribution, then any Derivative Works that You distribute must |
|
| 109 |
- include a readable copy of the attribution notices contained |
|
| 110 |
- within such NOTICE file, excluding those notices that do not |
|
| 111 |
- pertain to any part of the Derivative Works, in at least one |
|
| 112 |
- of the following places: within a NOTICE text file distributed |
|
| 113 |
- as part of the Derivative Works; within the Source form or |
|
| 114 |
- documentation, if provided along with the Derivative Works; or, |
|
| 115 |
- within a display generated by the Derivative Works, if and |
|
| 116 |
- wherever such third-party notices normally appear. The contents |
|
| 117 |
- of the NOTICE file are for informational purposes only and |
|
| 118 |
- do not modify the License. You may add Your own attribution |
|
| 119 |
- notices within Derivative Works that You distribute, alongside |
|
| 120 |
- or as an addendum to the NOTICE text from the Work, provided |
|
| 121 |
- that such additional attribution notices cannot be construed |
|
| 122 |
- as modifying the License. |
|
| 123 |
- |
|
| 124 |
- You may add Your own copyright statement to Your modifications and |
|
| 125 |
- may provide additional or different license terms and conditions |
|
| 126 |
- for use, reproduction, or distribution of Your modifications, or |
|
| 127 |
- for any such Derivative Works as a whole, provided Your use, |
|
| 128 |
- reproduction, and distribution of the Work otherwise complies with |
|
| 129 |
- the conditions stated in this License. |
|
| 130 |
- |
|
| 131 |
- 5. Submission of Contributions. Unless You explicitly state otherwise, |
|
| 132 |
- any Contribution intentionally submitted for inclusion in the Work |
|
| 133 |
- by You to the Licensor shall be under the terms and conditions of |
|
| 134 |
- this License, without any additional terms or conditions. |
|
| 135 |
- Notwithstanding the above, nothing herein shall supersede or modify |
|
| 136 |
- the terms of any separate license agreement you may have executed |
|
| 137 |
- with Licensor regarding such Contributions. |
|
| 138 |
- |
|
| 139 |
- 6. Trademarks. This License does not grant permission to use the trade |
|
| 140 |
- names, trademarks, service marks, or product names of the Licensor, |
|
| 141 |
- except as required for reasonable and customary use in describing the |
|
| 142 |
- origin of the Work and reproducing the content of the NOTICE file. |
|
| 143 |
- |
|
| 144 |
- 7. Disclaimer of Warranty. Unless required by applicable law or |
|
| 145 |
- agreed to in writing, Licensor provides the Work (and each |
|
| 146 |
- Contributor provides its Contributions) on an "AS IS" BASIS, |
|
| 147 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|
| 148 |
- implied, including, without limitation, any warranties or conditions |
|
| 149 |
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
|
| 150 |
- PARTICULAR PURPOSE. You are solely responsible for determining the |
|
| 151 |
- appropriateness of using or redistributing the Work and assume any |
|
| 152 |
- risks associated with Your exercise of permissions under this License. |
|
| 153 |
- |
|
| 154 |
- 8. Limitation of Liability. In no event and under no legal theory, |
|
| 155 |
- whether in tort (including negligence), contract, or otherwise, |
|
| 156 |
- unless required by applicable law (such as deliberate and grossly |
|
| 157 |
- negligent acts) or agreed to in writing, shall any Contributor be |
|
| 158 |
- liable to You for damages, including any direct, indirect, special, |
|
| 159 |
- incidental, or consequential damages of any character arising as a |
|
| 160 |
- result of this License or out of the use or inability to use the |
|
| 161 |
- Work (including but not limited to damages for loss of goodwill, |
|
| 162 |
- work stoppage, computer failure or malfunction, or any and all |
|
| 163 |
- other commercial damages or losses), even if such Contributor |
|
| 164 |
- has been advised of the possibility of such damages. |
|
| 165 |
- |
|
| 166 |
- 9. Accepting Warranty or Additional Liability. While redistributing |
|
| 167 |
- the Work or Derivative Works thereof, You may choose to offer, |
|
| 168 |
- and charge a fee for, acceptance of support, warranty, indemnity, |
|
| 169 |
- or other liability obligations and/or rights consistent with this |
|
| 170 |
- License. However, in accepting such obligations, You may act only |
|
| 171 |
- on Your own behalf and on Your sole responsibility, not on behalf |
|
| 172 |
- of any other Contributor, and only if You agree to indemnify, |
|
| 173 |
- defend, and hold each Contributor harmless for any liability |
|
| 174 |
- incurred by, or claims asserted against, such Contributor by reason |
|
| 175 |
- of your accepting any such warranty or additional liability. |
|
| 176 |
- |
|
| 177 |
- END OF TERMS AND CONDITIONS |
|
| 178 |
- |
|
| 179 |
- Copyright 2013-2016 Docker, Inc. |
|
| 180 |
- Copyright 2016 The OpenShift Authors |
|
| 181 |
- |
|
| 182 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 183 |
- you may not use this file except in compliance with the License. |
|
| 184 |
- You may obtain a copy of the License at |
|
| 185 |
- |
|
| 186 |
- https://www.apache.org/licenses/LICENSE-2.0 |
|
| 187 |
- |
|
| 188 |
- Unless required by applicable law or agreed to in writing, software |
|
| 189 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 190 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 191 |
- See the License for the specific language governing permissions and |
|
| 192 |
- limitations under the License. |
| 193 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,293 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "fmt" |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "log" |
|
| 8 |
- "os" |
|
| 9 |
- "path/filepath" |
|
| 10 |
- "runtime" |
|
| 11 |
- "strings" |
|
| 12 |
- |
|
| 13 |
- docker "github.com/fsouza/go-dockerclient" |
|
| 14 |
- |
|
| 15 |
- "github.com/docker/docker/builder/command" |
|
| 16 |
- "github.com/docker/docker/builder/parser" |
|
| 17 |
-) |
|
| 18 |
- |
|
| 19 |
-// Copy defines a copy operation required on the container. |
|
| 20 |
-type Copy struct {
|
|
| 21 |
- Src string |
|
| 22 |
- Dest []string |
|
| 23 |
- Download bool |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// Run defines a run operation required in the container. |
|
| 27 |
-type Run struct {
|
|
| 28 |
- Shell bool |
|
| 29 |
- Args []string |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-type Executor interface {
|
|
| 33 |
- Copy(copies ...Copy) error |
|
| 34 |
- Run(run Run, config docker.Config) error |
|
| 35 |
-} |
|
| 36 |
- |
|
| 37 |
-type logExecutor struct{}
|
|
| 38 |
- |
|
| 39 |
-func (logExecutor) Copy(copies ...Copy) error {
|
|
| 40 |
- for _, c := range copies {
|
|
| 41 |
- log.Printf("COPY %s -> %v (download:%t)", c.Src, c.Dest, c.Download)
|
|
| 42 |
- } |
|
| 43 |
- return nil |
|
| 44 |
-} |
|
| 45 |
- |
|
| 46 |
-func (logExecutor) Run(run Run, config docker.Config) error {
|
|
| 47 |
- log.Printf("RUN %v %t (%v)", run.Args, run.Shell, config.Env)
|
|
| 48 |
- return nil |
|
| 49 |
-} |
|
| 50 |
- |
|
| 51 |
-type noopExecutor struct{}
|
|
| 52 |
- |
|
| 53 |
-func (noopExecutor) Copy(copies ...Copy) error {
|
|
| 54 |
- return nil |
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-func (noopExecutor) Run(run Run, config docker.Config) error {
|
|
| 58 |
- return nil |
|
| 59 |
-} |
|
| 60 |
- |
|
| 61 |
-var ( |
|
| 62 |
- LogExecutor = logExecutor{}
|
|
| 63 |
- NoopExecutor = noopExecutor{}
|
|
| 64 |
-) |
|
| 65 |
- |
|
| 66 |
-type Builder struct {
|
|
| 67 |
- RunConfig docker.Config |
|
| 68 |
- |
|
| 69 |
- Env []string |
|
| 70 |
- Args map[string]string |
|
| 71 |
- CmdSet bool |
|
| 72 |
- Author string |
|
| 73 |
- |
|
| 74 |
- AllowedArgs map[string]bool |
|
| 75 |
- |
|
| 76 |
- PendingRuns []Run |
|
| 77 |
- PendingCopies []Copy |
|
| 78 |
- |
|
| 79 |
- Executor Executor |
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-func NewBuilder() *Builder {
|
|
| 83 |
- args := make(map[string]bool) |
|
| 84 |
- for k, v := range builtinAllowedBuildArgs {
|
|
| 85 |
- args[k] = v |
|
| 86 |
- } |
|
| 87 |
- return &Builder{
|
|
| 88 |
- Args: make(map[string]string), |
|
| 89 |
- AllowedArgs: args, |
|
| 90 |
- } |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-// Step creates a new step from the current state. |
|
| 94 |
-func (b *Builder) Step() *Step {
|
|
| 95 |
- dst := make([]string, len(b.Env)+len(b.RunConfig.Env)) |
|
| 96 |
- copy(dst, b.Env) |
|
| 97 |
- dst = append(dst, b.RunConfig.Env...) |
|
| 98 |
- return &Step{Env: dst}
|
|
| 99 |
-} |
|
| 100 |
- |
|
| 101 |
-// Run executes a step, transforming the current builder and |
|
| 102 |
-// invoking any Copy or Run operations. |
|
| 103 |
-func (b *Builder) Run(step *Step, exec Executor) error {
|
|
| 104 |
- fn, ok := evaluateTable[step.Command] |
|
| 105 |
- if !ok {
|
|
| 106 |
- return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(step.Command))
|
|
| 107 |
- } |
|
| 108 |
- if err := fn(b, step.Args, step.Attrs, step.Original); err != nil {
|
|
| 109 |
- return err |
|
| 110 |
- } |
|
| 111 |
- |
|
| 112 |
- copies := b.PendingCopies |
|
| 113 |
- b.PendingCopies = nil |
|
| 114 |
- runs := b.PendingRuns |
|
| 115 |
- b.PendingRuns = nil |
|
| 116 |
- |
|
| 117 |
- if err := exec.Copy(copies...); err != nil {
|
|
| 118 |
- return err |
|
| 119 |
- } |
|
| 120 |
- for _, run := range runs {
|
|
| 121 |
- config := b.Config() |
|
| 122 |
- if err := exec.Run(run, *config); err != nil {
|
|
| 123 |
- return err |
|
| 124 |
- } |
|
| 125 |
- } |
|
| 126 |
- |
|
| 127 |
- return nil |
|
| 128 |
-} |
|
| 129 |
- |
|
| 130 |
-// RequiresStart returns true if a running container environment is necessary |
|
| 131 |
-// to invoke the provided commands |
|
| 132 |
-func (b *Builder) RequiresStart(node *parser.Node) bool {
|
|
| 133 |
- for _, child := range node.Children {
|
|
| 134 |
- if child.Value == command.Run {
|
|
| 135 |
- return true |
|
| 136 |
- } |
|
| 137 |
- } |
|
| 138 |
- return false |
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-// Config returns a snapshot of the current RunConfig intended for |
|
| 142 |
-// use with a container commit. |
|
| 143 |
-func (b *Builder) Config() *docker.Config {
|
|
| 144 |
- config := b.RunConfig |
|
| 145 |
- if config.OnBuild == nil {
|
|
| 146 |
- config.OnBuild = []string{}
|
|
| 147 |
- } |
|
| 148 |
- if config.Entrypoint == nil {
|
|
| 149 |
- config.Entrypoint = []string{}
|
|
| 150 |
- } |
|
| 151 |
- config.Image = "" |
|
| 152 |
- return &config |
|
| 153 |
-} |
|
| 154 |
- |
|
| 155 |
-// Arguments returns the currently active arguments. |
|
| 156 |
-func (b *Builder) Arguments() []string {
|
|
| 157 |
- var envs []string |
|
| 158 |
- for key, val := range b.Args {
|
|
| 159 |
- if _, ok := b.AllowedArgs[key]; ok {
|
|
| 160 |
- envs = append(envs, fmt.Sprintf("%s=%s", key, val))
|
|
| 161 |
- } |
|
| 162 |
- } |
|
| 163 |
- return envs |
|
| 164 |
-} |
|
| 165 |
- |
|
| 166 |
-// ErrNoFROM is returned if the Dockerfile did not contain a FROM |
|
| 167 |
-// statement. |
|
| 168 |
-var ErrNoFROM = fmt.Errorf("no FROM statement found")
|
|
| 169 |
- |
|
| 170 |
-// From returns the image this dockerfile depends on, or an error |
|
| 171 |
-// if no FROM is found or if multiple FROM are specified. If a |
|
| 172 |
-// single from is found the passed node is updated with only |
|
| 173 |
-// the remaining statements. The builder's RunConfig.Image field |
|
| 174 |
-// is set to the first From found, or left unchanged if already |
|
| 175 |
-// set. |
|
| 176 |
-func (b *Builder) From(node *parser.Node) (string, error) {
|
|
| 177 |
- children := SplitChildren(node, command.From) |
|
| 178 |
- switch {
|
|
| 179 |
- case len(children) == 0: |
|
| 180 |
- return "", ErrNoFROM |
|
| 181 |
- case len(children) > 1: |
|
| 182 |
- return "", fmt.Errorf("multiple FROM statements are not supported")
|
|
| 183 |
- default: |
|
| 184 |
- step := b.Step() |
|
| 185 |
- if err := step.Resolve(children[0]); err != nil {
|
|
| 186 |
- return "", err |
|
| 187 |
- } |
|
| 188 |
- if err := b.Run(step, NoopExecutor); err != nil {
|
|
| 189 |
- return "", err |
|
| 190 |
- } |
|
| 191 |
- return b.RunConfig.Image, nil |
|
| 192 |
- } |
|
| 193 |
-} |
|
| 194 |
- |
|
| 195 |
-// FromImage updates the builder to use the provided image (resetting RunConfig |
|
| 196 |
-// and recording the image environment), and updates the node with any ONBUILD |
|
| 197 |
-// statements extracted from the parent image. |
|
| 198 |
-func (b *Builder) FromImage(image *docker.Image, node *parser.Node) error {
|
|
| 199 |
- SplitChildren(node, command.From) |
|
| 200 |
- |
|
| 201 |
- b.RunConfig = *image.Config |
|
| 202 |
- b.Env = b.RunConfig.Env |
|
| 203 |
- b.RunConfig.Env = nil |
|
| 204 |
- |
|
| 205 |
- // Check to see if we have a default PATH, note that windows won't |
|
| 206 |
- // have one as its set by HCS |
|
| 207 |
- if runtime.GOOS != "windows" && !hasEnvName(b.Env, "PATH") {
|
|
| 208 |
- b.RunConfig.Env = append(b.RunConfig.Env, "PATH="+defaultPathEnv) |
|
| 209 |
- } |
|
| 210 |
- |
|
| 211 |
- // Join the image onbuild statements into node |
|
| 212 |
- if image.Config == nil || len(image.Config.OnBuild) == 0 {
|
|
| 213 |
- return nil |
|
| 214 |
- } |
|
| 215 |
- extra, err := parser.Parse(bytes.NewBufferString(strings.Join(image.Config.OnBuild, "\n"))) |
|
| 216 |
- if err != nil {
|
|
| 217 |
- return err |
|
| 218 |
- } |
|
| 219 |
- for _, child := range extra.Children {
|
|
| 220 |
- switch strings.ToUpper(child.Value) {
|
|
| 221 |
- case "ONBUILD": |
|
| 222 |
- return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
|
| 223 |
- case "MAINTAINER", "FROM": |
|
| 224 |
- return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", child.Value)
|
|
| 225 |
- } |
|
| 226 |
- } |
|
| 227 |
- node.Children = append(extra.Children, node.Children...) |
|
| 228 |
- // Since we've processed the OnBuild statements, clear them from the runconfig state. |
|
| 229 |
- b.RunConfig.OnBuild = nil |
|
| 230 |
- return nil |
|
| 231 |
-} |
|
| 232 |
- |
|
| 233 |
-// SplitChildren removes any children with the provided value from node |
|
| 234 |
-// and returns them as an array. node.Children is updated. |
|
| 235 |
-func SplitChildren(node *parser.Node, value string) []*parser.Node {
|
|
| 236 |
- var split []*parser.Node |
|
| 237 |
- var children []*parser.Node |
|
| 238 |
- for _, child := range node.Children {
|
|
| 239 |
- if child.Value == value {
|
|
| 240 |
- split = append(split, child) |
|
| 241 |
- } else {
|
|
| 242 |
- children = append(children, child) |
|
| 243 |
- } |
|
| 244 |
- } |
|
| 245 |
- node.Children = children |
|
| 246 |
- return split |
|
| 247 |
-} |
|
| 248 |
- |
|
| 249 |
-// StepFunc is invoked with the result of a resolved step. |
|
| 250 |
-type StepFunc func(*Builder, []string, map[string]bool, string) error |
|
| 251 |
- |
|
| 252 |
-var evaluateTable = map[string]StepFunc{
|
|
| 253 |
- command.Env: env, |
|
| 254 |
- command.Label: label, |
|
| 255 |
- command.Maintainer: maintainer, |
|
| 256 |
- command.Add: add, |
|
| 257 |
- command.Copy: dispatchCopy, // copy() is a go builtin |
|
| 258 |
- command.From: from, |
|
| 259 |
- command.Onbuild: onbuild, |
|
| 260 |
- command.Workdir: workdir, |
|
| 261 |
- command.Run: run, |
|
| 262 |
- command.Cmd: cmd, |
|
| 263 |
- command.Entrypoint: entrypoint, |
|
| 264 |
- command.Expose: expose, |
|
| 265 |
- command.Volume: volume, |
|
| 266 |
- command.User: user, |
|
| 267 |
- // TODO: use the public constants for these when we update dockerfile/ |
|
| 268 |
- commandStopSignal: stopSignal, |
|
| 269 |
- commandArg: arg, |
|
| 270 |
-} |
|
| 271 |
- |
|
| 272 |
-// builtinAllowedBuildArgs is list of built-in allowed build args |
|
| 273 |
-var builtinAllowedBuildArgs = map[string]bool{
|
|
| 274 |
- "HTTP_PROXY": true, |
|
| 275 |
- "http_proxy": true, |
|
| 276 |
- "HTTPS_PROXY": true, |
|
| 277 |
- "https_proxy": true, |
|
| 278 |
- "FTP_PROXY": true, |
|
| 279 |
- "ftp_proxy": true, |
|
| 280 |
- "NO_PROXY": true, |
|
| 281 |
- "no_proxy": true, |
|
| 282 |
-} |
|
| 283 |
- |
|
| 284 |
-// ParseDockerIgnore returns a list of the excludes in the .dockerignore file. |
|
| 285 |
-// extracted from fsouza/go-dockerclient. |
|
| 286 |
-func ParseDockerignore(root string) ([]string, error) {
|
|
| 287 |
- var excludes []string |
|
| 288 |
- ignore, err := ioutil.ReadFile(filepath.Join(root, ".dockerignore")) |
|
| 289 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 290 |
- return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err)
|
|
| 291 |
- } |
|
| 292 |
- return strings.Split(string(ignore), "\n"), nil |
|
| 293 |
-} |
| 294 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,350 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "os" |
|
| 7 |
- "testing" |
|
| 8 |
- |
|
| 9 |
- "fmt" |
|
| 10 |
- "github.com/docker/docker/builder/parser" |
|
| 11 |
- docker "github.com/fsouza/go-dockerclient" |
|
| 12 |
- "reflect" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-func TestRun(t *testing.T) {
|
|
| 16 |
- f, err := os.Open("../../../../../images/dockerregistry/Dockerfile")
|
|
| 17 |
- if err != nil {
|
|
| 18 |
- t.Fatal(err) |
|
| 19 |
- } |
|
| 20 |
- node, err := parser.Parse(f) |
|
| 21 |
- if err != nil {
|
|
| 22 |
- t.Fatal(err) |
|
| 23 |
- } |
|
| 24 |
- b := NewBuilder() |
|
| 25 |
- from, err := b.From(node) |
|
| 26 |
- if err != nil {
|
|
| 27 |
- t.Fatal(err) |
|
| 28 |
- } |
|
| 29 |
- if from != "openshift/origin-base" {
|
|
| 30 |
- t.Fatalf("unexpected from: %s", from)
|
|
| 31 |
- } |
|
| 32 |
- for _, child := range node.Children {
|
|
| 33 |
- step := b.Step() |
|
| 34 |
- if err := step.Resolve(child); err != nil {
|
|
| 35 |
- t.Fatal(err) |
|
| 36 |
- } |
|
| 37 |
- if err := b.Run(step, LogExecutor); err != nil {
|
|
| 38 |
- t.Fatal(err) |
|
| 39 |
- } |
|
| 40 |
- } |
|
| 41 |
- t.Logf("config: %#v", b.Config())
|
|
| 42 |
- t.Logf(node.Dump()) |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-type testExecutor struct {
|
|
| 46 |
- Copies []Copy |
|
| 47 |
- Runs []Run |
|
| 48 |
- Configs []docker.Config |
|
| 49 |
- Err error |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-func (e *testExecutor) Copy(copies ...Copy) error {
|
|
| 53 |
- e.Copies = append(e.Copies, copies...) |
|
| 54 |
- return e.Err |
|
| 55 |
-} |
|
| 56 |
-func (e *testExecutor) Run(run Run, config docker.Config) error {
|
|
| 57 |
- e.Runs = append(e.Runs, run) |
|
| 58 |
- e.Configs = append(e.Configs, config) |
|
| 59 |
- return e.Err |
|
| 60 |
-} |
|
| 61 |
- |
|
| 62 |
-func TestBuilder(t *testing.T) {
|
|
| 63 |
- testCases := []struct {
|
|
| 64 |
- Dockerfile string |
|
| 65 |
- From string |
|
| 66 |
- Copies []Copy |
|
| 67 |
- Runs []Run |
|
| 68 |
- Config docker.Config |
|
| 69 |
- ErrFn func(err error) bool |
|
| 70 |
- }{
|
|
| 71 |
- {
|
|
| 72 |
- Dockerfile: "testdata/dir/Dockerfile", |
|
| 73 |
- From: "busybox", |
|
| 74 |
- Copies: []Copy{
|
|
| 75 |
- {Src: ".", Dest: []string{"/"}, Download: false},
|
|
| 76 |
- {Src: ".", Dest: []string{"/dir"}},
|
|
| 77 |
- {Src: "subdir/", Dest: []string{"/test/"}, Download: false},
|
|
| 78 |
- }, |
|
| 79 |
- Config: docker.Config{
|
|
| 80 |
- Image: "busybox", |
|
| 81 |
- }, |
|
| 82 |
- }, |
|
| 83 |
- {
|
|
| 84 |
- Dockerfile: "testdata/ignore/Dockerfile", |
|
| 85 |
- From: "busybox", |
|
| 86 |
- Copies: []Copy{
|
|
| 87 |
- {Src: ".", Dest: []string{"/"}},
|
|
| 88 |
- }, |
|
| 89 |
- Config: docker.Config{
|
|
| 90 |
- Image: "busybox", |
|
| 91 |
- }, |
|
| 92 |
- }, |
|
| 93 |
- {
|
|
| 94 |
- Dockerfile: "testdata/Dockerfile.env", |
|
| 95 |
- From: "busybox", |
|
| 96 |
- Config: docker.Config{
|
|
| 97 |
- Env: []string{"name=value", "name2=value2a value2b", "name1=value1", "name3=value3a\\n\"value3b\"", "name4=value4a\\\\nvalue4b"},
|
|
| 98 |
- Image: "busybox", |
|
| 99 |
- }, |
|
| 100 |
- }, |
|
| 101 |
- {
|
|
| 102 |
- Dockerfile: "testdata/Dockerfile.edgecases", |
|
| 103 |
- From: "busybox", |
|
| 104 |
- Copies: []Copy{
|
|
| 105 |
- {Src: ".", Dest: []string{"/"}, Download: true},
|
|
| 106 |
- {Src: ".", Dest: []string{"/test/copy"}},
|
|
| 107 |
- }, |
|
| 108 |
- Runs: []Run{
|
|
| 109 |
- {Shell: false, Args: []string{"ls", "-la"}},
|
|
| 110 |
- {Shell: false, Args: []string{"echo", "'1234'"}},
|
|
| 111 |
- {Shell: true, Args: []string{"echo \"1234\""}},
|
|
| 112 |
- {Shell: true, Args: []string{"echo 1234"}},
|
|
| 113 |
- {Shell: true, Args: []string{"echo '1234' && echo \"456\" && echo 789"}},
|
|
| 114 |
- {Shell: true, Args: []string{"sh -c 'echo root:testpass > /tmp/passwd'"}},
|
|
| 115 |
- {Shell: true, Args: []string{"mkdir -p /test /test2 /test3/test"}},
|
|
| 116 |
- }, |
|
| 117 |
- Config: docker.Config{
|
|
| 118 |
- User: "docker:root", |
|
| 119 |
- ExposedPorts: map[docker.Port]struct{}{"6000/tcp": {}, "3000/tcp": {}, "9000/tcp": {}, "5000/tcp": {}},
|
|
| 120 |
- Env: []string{"SCUBA=1 DUBA 3"},
|
|
| 121 |
- Cmd: []string{"/bin/sh", "-c", "echo 'test' | wc -"},
|
|
| 122 |
- Image: "busybox", |
|
| 123 |
- Volumes: map[string]struct{}{"/test2": {}, "/test3": {}, "/test": {}},
|
|
| 124 |
- WorkingDir: "/test", |
|
| 125 |
- OnBuild: []string{"RUN [\"echo\", \"test\"]", "RUN echo test", "COPY . /"},
|
|
| 126 |
- }, |
|
| 127 |
- }, |
|
| 128 |
- {
|
|
| 129 |
- Dockerfile: "testdata/Dockerfile.exposedefault", |
|
| 130 |
- From: "busybox", |
|
| 131 |
- Config: docker.Config{
|
|
| 132 |
- ExposedPorts: map[docker.Port]struct{}{"3469/tcp": {}},
|
|
| 133 |
- Image: "busybox", |
|
| 134 |
- }, |
|
| 135 |
- }, |
|
| 136 |
- {
|
|
| 137 |
- Dockerfile: "testdata/Dockerfile.add", |
|
| 138 |
- From: "busybox", |
|
| 139 |
- Copies: []Copy{
|
|
| 140 |
- {Src: "https://github.com/openshift/origin/raw/master/README.md", Dest: []string{"/README.md"}, Download: true},
|
|
| 141 |
- {Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/"}, Download: true},
|
|
| 142 |
- {Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/A"}, Download: true},
|
|
| 143 |
- {Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/a"}, Download: true},
|
|
| 144 |
- {Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/b/a"}, Download: true},
|
|
| 145 |
- {Src: "https://github.com/openshift/origin/raw/master/LICENSE", Dest: []string{"/b/"}, Download: true},
|
|
| 146 |
- {Src: "https://github.com/openshift/ruby-hello-world/archive/master.zip", Dest: []string{"/tmp/"}, Download: true},
|
|
| 147 |
- }, |
|
| 148 |
- Runs: []Run{
|
|
| 149 |
- {Shell: true, Args: []string{"mkdir ./b"}},
|
|
| 150 |
- }, |
|
| 151 |
- Config: docker.Config{
|
|
| 152 |
- Image: "busybox", |
|
| 153 |
- User: "root", |
|
| 154 |
- }, |
|
| 155 |
- }, |
|
| 156 |
- } |
|
| 157 |
- for i, test := range testCases {
|
|
| 158 |
- data, err := ioutil.ReadFile(test.Dockerfile) |
|
| 159 |
- if err != nil {
|
|
| 160 |
- t.Errorf("%d: %v", i, err)
|
|
| 161 |
- continue |
|
| 162 |
- } |
|
| 163 |
- node, err := parser.Parse(bytes.NewBuffer(data)) |
|
| 164 |
- if err != nil {
|
|
| 165 |
- t.Errorf("%d: %v", i, err)
|
|
| 166 |
- continue |
|
| 167 |
- } |
|
| 168 |
- b := NewBuilder() |
|
| 169 |
- from, err := b.From(node) |
|
| 170 |
- if err != nil {
|
|
| 171 |
- t.Errorf("%d: %v", i, err)
|
|
| 172 |
- continue |
|
| 173 |
- } |
|
| 174 |
- if from != test.From {
|
|
| 175 |
- t.Errorf("%d: unexpected FROM: %s", i, from)
|
|
| 176 |
- } |
|
| 177 |
- e := &testExecutor{}
|
|
| 178 |
- var lastErr error |
|
| 179 |
- for j, child := range node.Children {
|
|
| 180 |
- step := b.Step() |
|
| 181 |
- if err := step.Resolve(child); err != nil {
|
|
| 182 |
- lastErr = fmt.Errorf("%d: %d: %s: resolve: %v", i, j, step.Original, err)
|
|
| 183 |
- break |
|
| 184 |
- } |
|
| 185 |
- if err := b.Run(step, e); err != nil {
|
|
| 186 |
- lastErr = fmt.Errorf("%d: %d: %s: run: %v", i, j, step.Original, err)
|
|
| 187 |
- break |
|
| 188 |
- } |
|
| 189 |
- } |
|
| 190 |
- if lastErr != nil {
|
|
| 191 |
- if test.ErrFn == nil || !test.ErrFn(lastErr) {
|
|
| 192 |
- t.Errorf("%d: unexpected error: %v", i, lastErr)
|
|
| 193 |
- } |
|
| 194 |
- continue |
|
| 195 |
- } |
|
| 196 |
- if !reflect.DeepEqual(test.Copies, e.Copies) {
|
|
| 197 |
- t.Errorf("%d: unexpected copies: %#v", i, e.Copies)
|
|
| 198 |
- } |
|
| 199 |
- if !reflect.DeepEqual(test.Runs, e.Runs) {
|
|
| 200 |
- t.Errorf("%d: unexpected runs: %#v", i, e.Runs)
|
|
| 201 |
- } |
|
| 202 |
- lastConfig := b.RunConfig |
|
| 203 |
- if !reflect.DeepEqual(test.Config, lastConfig) {
|
|
| 204 |
- t.Errorf("%d: unexpected config: %#v", i, lastConfig)
|
|
| 205 |
- } |
|
| 206 |
- } |
|
| 207 |
-} |
|
| 208 |
- |
|
| 209 |
-func TestCalcCopyInfo(t *testing.T) {
|
|
| 210 |
- nilErr := func(err error) bool { return err == nil }
|
|
| 211 |
- tests := []struct {
|
|
| 212 |
- origPath string |
|
| 213 |
- rootPath string |
|
| 214 |
- dstPath string |
|
| 215 |
- allowWildcards bool |
|
| 216 |
- errFn func(err error) bool |
|
| 217 |
- paths map[string]struct{}
|
|
| 218 |
- excludes []string |
|
| 219 |
- rebaseNames map[string]string |
|
| 220 |
- }{
|
|
| 221 |
- {
|
|
| 222 |
- origPath: "subdir/*", |
|
| 223 |
- rootPath: "testdata/dir", |
|
| 224 |
- allowWildcards: true, |
|
| 225 |
- errFn: nilErr, |
|
| 226 |
- paths: map[string]struct{}{"subdir/file2": {}},
|
|
| 227 |
- }, |
|
| 228 |
- {
|
|
| 229 |
- origPath: "*", |
|
| 230 |
- rootPath: "testdata/dir", |
|
| 231 |
- allowWildcards: true, |
|
| 232 |
- errFn: nilErr, |
|
| 233 |
- paths: map[string]struct{}{
|
|
| 234 |
- "Dockerfile": {},
|
|
| 235 |
- "file": {},
|
|
| 236 |
- "subdir": {},
|
|
| 237 |
- }, |
|
| 238 |
- }, |
|
| 239 |
- {
|
|
| 240 |
- origPath: ".", |
|
| 241 |
- rootPath: "testdata/dir", |
|
| 242 |
- allowWildcards: true, |
|
| 243 |
- errFn: nilErr, |
|
| 244 |
- paths: map[string]struct{}{
|
|
| 245 |
- "Dockerfile": {},
|
|
| 246 |
- "file": {},
|
|
| 247 |
- "subdir": {},
|
|
| 248 |
- }, |
|
| 249 |
- }, |
|
| 250 |
- {
|
|
| 251 |
- origPath: "/.", |
|
| 252 |
- rootPath: "testdata/dir", |
|
| 253 |
- allowWildcards: true, |
|
| 254 |
- errFn: nilErr, |
|
| 255 |
- paths: map[string]struct{}{
|
|
| 256 |
- "Dockerfile": {},
|
|
| 257 |
- "file": {},
|
|
| 258 |
- "subdir": {},
|
|
| 259 |
- }, |
|
| 260 |
- }, |
|
| 261 |
- {
|
|
| 262 |
- origPath: "subdir/", |
|
| 263 |
- rootPath: "testdata/dir", |
|
| 264 |
- allowWildcards: true, |
|
| 265 |
- errFn: nilErr, |
|
| 266 |
- paths: map[string]struct{}{
|
|
| 267 |
- "subdir/": {},
|
|
| 268 |
- }, |
|
| 269 |
- }, |
|
| 270 |
- {
|
|
| 271 |
- origPath: "subdir", |
|
| 272 |
- rootPath: "testdata/dir", |
|
| 273 |
- allowWildcards: true, |
|
| 274 |
- errFn: nilErr, |
|
| 275 |
- paths: map[string]struct{}{
|
|
| 276 |
- "subdir": {},
|
|
| 277 |
- }, |
|
| 278 |
- }, |
|
| 279 |
- {
|
|
| 280 |
- origPath: "subdir/.", |
|
| 281 |
- rootPath: "testdata/dir", |
|
| 282 |
- allowWildcards: true, |
|
| 283 |
- errFn: nilErr, |
|
| 284 |
- paths: map[string]struct{}{
|
|
| 285 |
- "subdir/": {},
|
|
| 286 |
- }, |
|
| 287 |
- }, |
|
| 288 |
- {
|
|
| 289 |
- origPath: "testdata/dir/subdir/.", |
|
| 290 |
- rootPath: "", |
|
| 291 |
- allowWildcards: true, |
|
| 292 |
- errFn: nilErr, |
|
| 293 |
- paths: map[string]struct{}{
|
|
| 294 |
- "testdata/dir/subdir/": {},
|
|
| 295 |
- }, |
|
| 296 |
- }, |
|
| 297 |
- {
|
|
| 298 |
- origPath: "subdir/", |
|
| 299 |
- rootPath: "testdata/dir", |
|
| 300 |
- allowWildcards: true, |
|
| 301 |
- errFn: nilErr, |
|
| 302 |
- paths: map[string]struct{}{
|
|
| 303 |
- "subdir/": {},
|
|
| 304 |
- }, |
|
| 305 |
- }, |
|
| 306 |
- {
|
|
| 307 |
- origPath: "subdir/", |
|
| 308 |
- rootPath: "testdata/dir", |
|
| 309 |
- allowWildcards: true, |
|
| 310 |
- errFn: nilErr, |
|
| 311 |
- paths: map[string]struct{}{
|
|
| 312 |
- "subdir/": {},
|
|
| 313 |
- }, |
|
| 314 |
- dstPath: "test/", |
|
| 315 |
- rebaseNames: map[string]string{
|
|
| 316 |
- "subdir/": "test/", |
|
| 317 |
- }, |
|
| 318 |
- }, |
|
| 319 |
- } |
|
| 320 |
- |
|
| 321 |
- for i, test := range tests {
|
|
| 322 |
- infos, err := CalcCopyInfo(test.origPath, test.rootPath, false, test.allowWildcards) |
|
| 323 |
- if !test.errFn(err) {
|
|
| 324 |
- t.Errorf("%d: unexpected error: %v", i, err)
|
|
| 325 |
- continue |
|
| 326 |
- } |
|
| 327 |
- if err != nil {
|
|
| 328 |
- continue |
|
| 329 |
- } |
|
| 330 |
- expect := make(map[string]struct{})
|
|
| 331 |
- for k := range test.paths {
|
|
| 332 |
- expect[k] = struct{}{}
|
|
| 333 |
- } |
|
| 334 |
- for _, info := range infos {
|
|
| 335 |
- if _, ok := expect[info.Path]; ok {
|
|
| 336 |
- delete(expect, info.Path) |
|
| 337 |
- } else {
|
|
| 338 |
- t.Errorf("%d: did not expect path %s", i, info.Path)
|
|
| 339 |
- } |
|
| 340 |
- } |
|
| 341 |
- if len(expect) > 0 {
|
|
| 342 |
- t.Errorf("%d: did not see paths: %#v", i, expect)
|
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- options := archiveOptionsFor(infos, test.dstPath, test.excludes) |
|
| 346 |
- if !reflect.DeepEqual(test.rebaseNames, options.RebaseNames) {
|
|
| 347 |
- t.Errorf("%d: rebase names did not match: %#v", i, options.RebaseNames)
|
|
| 348 |
- } |
|
| 349 |
- } |
|
| 350 |
-} |
| 351 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,570 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "archive/tar" |
|
| 5 |
- "bytes" |
|
| 6 |
- "crypto/rand" |
|
| 7 |
- "fmt" |
|
| 8 |
- "io" |
|
| 9 |
- "os" |
|
| 10 |
- "path" |
|
| 11 |
- "path/filepath" |
|
| 12 |
- "runtime" |
|
| 13 |
- "strconv" |
|
| 14 |
- "strings" |
|
| 15 |
- |
|
| 16 |
- "k8s.io/kubernetes/pkg/credentialprovider" |
|
| 17 |
- |
|
| 18 |
- "github.com/docker/docker/builder/parser" |
|
| 19 |
- docker "github.com/fsouza/go-dockerclient" |
|
| 20 |
- "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive" |
|
| 21 |
- "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils" |
|
| 22 |
- "github.com/golang/glog" |
|
| 23 |
- |
|
| 24 |
- "github.com/openshift/origin/pkg/util/docker/dockerfile/builder/imageprogress" |
|
| 25 |
-) |
|
| 26 |
- |
|
| 27 |
-// Mount represents a binding between the current system and the destination client |
|
| 28 |
-type Mount struct {
|
|
| 29 |
- SourcePath string |
|
| 30 |
- DestinationPath string |
|
| 31 |
-} |
|
| 32 |
- |
|
| 33 |
-// ClientExecutor can run Docker builds from a Docker client. |
|
| 34 |
-type ClientExecutor struct {
|
|
| 35 |
- // Client is a client to a Docker daemon. |
|
| 36 |
- Client *docker.Client |
|
| 37 |
- // Directory is the context directory to build from, will use |
|
| 38 |
- // the current working directory if not set. |
|
| 39 |
- Directory string |
|
| 40 |
- // Excludes are a list of file patterns that should be excluded |
|
| 41 |
- // from the context. Will be set to the contents of the |
|
| 42 |
- // .dockerignore file if nil. |
|
| 43 |
- Excludes []string |
|
| 44 |
- // Tag is an optional value to tag the resulting built image. |
|
| 45 |
- Tag string |
|
| 46 |
- // AllowPull when set will pull images that are not present on |
|
| 47 |
- // the daemon. |
|
| 48 |
- AllowPull bool |
|
| 49 |
- // TransientMounts are a set of mounts from outside the build |
|
| 50 |
- // to the inside that will not be part of the final image. Any |
|
| 51 |
- // content created inside the mount's destinationPath will be |
|
| 52 |
- // omitted from the final image. |
|
| 53 |
- TransientMounts []Mount |
|
| 54 |
- |
|
| 55 |
- Out, ErrOut io.Writer |
|
| 56 |
- |
|
| 57 |
- // Container is optional and can be set to a container to use as |
|
| 58 |
- // the execution environment for a build. |
|
| 59 |
- Container *docker.Container |
|
| 60 |
- // Command, if set, will be used as the entrypoint for the new |
|
| 61 |
- // container. This is ignored if Container is set. |
|
| 62 |
- Command []string |
|
| 63 |
- // Image is optional and may be set to control which image is used |
|
| 64 |
- // as a base for this build. Otherwise the FROM value from the |
|
| 65 |
- // Dockerfile is read (will be pulled if not locally present). |
|
| 66 |
- Image *docker.Image |
|
| 67 |
- |
|
| 68 |
- // AuthFn will handle authenticating any docker pulls if Image |
|
| 69 |
- // is set to nil. |
|
| 70 |
- AuthFn func(name string) ([]credentialprovider.LazyAuthConfiguration, bool) |
|
| 71 |
- // HostConfig is used to start the container (if necessary). |
|
| 72 |
- HostConfig *docker.HostConfig |
|
| 73 |
- // LogFn is an optional command to log information to the end user |
|
| 74 |
- LogFn func(format string, args ...interface{})
|
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 |
-// NewClientExecutor creates a client executor. |
|
| 78 |
-func NewClientExecutor(client *docker.Client) *ClientExecutor {
|
|
| 79 |
- return &ClientExecutor{Client: client}
|
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-// Build is a helper method to perform a Docker build against the |
|
| 83 |
-// provided Docker client. It will load the image if not specified, |
|
| 84 |
-// create a container if one does not already exist, and start a |
|
| 85 |
-// container if the Dockerfile contains RUN commands. It will cleanup |
|
| 86 |
-// any containers it creates directly, and set the e.Image.ID field |
|
| 87 |
-// to the generated image. |
|
| 88 |
-func (e *ClientExecutor) Build(r io.Reader, args map[string]string) error {
|
|
| 89 |
- b := NewBuilder() |
|
| 90 |
- b.Args = args |
|
| 91 |
- |
|
| 92 |
- if e.Excludes == nil {
|
|
| 93 |
- excludes, err := ParseDockerignore(e.Directory) |
|
| 94 |
- if err != nil {
|
|
| 95 |
- return err |
|
| 96 |
- } |
|
| 97 |
- e.Excludes = append(excludes, ".dockerignore") |
|
| 98 |
- } |
|
| 99 |
- |
|
| 100 |
- // TODO: check the Docker daemon version (1.20 is required for Upload) |
|
| 101 |
- |
|
| 102 |
- node, err := parser.Parse(r) |
|
| 103 |
- if err != nil {
|
|
| 104 |
- return err |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- // identify the base image |
|
| 108 |
- from, err := b.From(node) |
|
| 109 |
- if err != nil {
|
|
| 110 |
- return err |
|
| 111 |
- } |
|
| 112 |
- // load the image |
|
| 113 |
- if e.Image == nil {
|
|
| 114 |
- if from == NoBaseImageSpecifier {
|
|
| 115 |
- if runtime.GOOS == "windows" {
|
|
| 116 |
- return fmt.Errorf("building from scratch images is not supported")
|
|
| 117 |
- } |
|
| 118 |
- from, err = e.CreateScratchImage() |
|
| 119 |
- if err != nil {
|
|
| 120 |
- return fmt.Errorf("unable to create a scratch image for this build: %v", err)
|
|
| 121 |
- } |
|
| 122 |
- defer e.CleanupImage(from) |
|
| 123 |
- } |
|
| 124 |
- glog.V(4).Infof("Retrieving image %q", from)
|
|
| 125 |
- e.Image, err = e.LoadImage(from) |
|
| 126 |
- if err != nil {
|
|
| 127 |
- return err |
|
| 128 |
- } |
|
| 129 |
- } |
|
| 130 |
- |
|
| 131 |
- // update the builder with any information from the image, including ONBUILD |
|
| 132 |
- // statements |
|
| 133 |
- if err := b.FromImage(e.Image, node); err != nil {
|
|
| 134 |
- return err |
|
| 135 |
- } |
|
| 136 |
- |
|
| 137 |
- b.RunConfig.Image = from |
|
| 138 |
- e.LogFn("FROM %s", from)
|
|
| 139 |
- glog.V(4).Infof("step: FROM %s", from)
|
|
| 140 |
- |
|
| 141 |
- var sharedMount string |
|
| 142 |
- |
|
| 143 |
- // create a container to execute in, if necessary |
|
| 144 |
- mustStart := b.RequiresStart(node) |
|
| 145 |
- if e.Container == nil {
|
|
| 146 |
- opts := docker.CreateContainerOptions{
|
|
| 147 |
- Config: &docker.Config{
|
|
| 148 |
- Image: from, |
|
| 149 |
- }, |
|
| 150 |
- } |
|
| 151 |
- if mustStart {
|
|
| 152 |
- // Transient mounts only make sense on images that will be running processes |
|
| 153 |
- if len(e.TransientMounts) > 0 {
|
|
| 154 |
- volumeName, err := randSeq(imageSafeCharacters, 24) |
|
| 155 |
- if err != nil {
|
|
| 156 |
- return err |
|
| 157 |
- } |
|
| 158 |
- v, err := e.Client.CreateVolume(docker.CreateVolumeOptions{Name: volumeName})
|
|
| 159 |
- if err != nil {
|
|
| 160 |
- return fmt.Errorf("unable to create volume to mount secrets: %v", err)
|
|
| 161 |
- } |
|
| 162 |
- defer e.cleanupVolume(volumeName) |
|
| 163 |
- sharedMount = v.Mountpoint |
|
| 164 |
- opts.HostConfig = &docker.HostConfig{
|
|
| 165 |
- Binds: []string{sharedMount + ":/tmp/__temporarymount"},
|
|
| 166 |
- } |
|
| 167 |
- } |
|
| 168 |
- |
|
| 169 |
- // TODO: windows support |
|
| 170 |
- if len(e.Command) > 0 {
|
|
| 171 |
- opts.Config.Cmd = e.Command |
|
| 172 |
- opts.Config.Entrypoint = nil |
|
| 173 |
- } else {
|
|
| 174 |
- // TODO; replace me with a better default command |
|
| 175 |
- opts.Config.Cmd = []string{"sleep 86400"}
|
|
| 176 |
- opts.Config.Entrypoint = []string{"/bin/sh", "-c"}
|
|
| 177 |
- } |
|
| 178 |
- } |
|
| 179 |
- if len(opts.Config.Cmd) == 0 {
|
|
| 180 |
- opts.Config.Entrypoint = []string{"/bin/sh", "-c", "# NOP"}
|
|
| 181 |
- } |
|
| 182 |
- container, err := e.Client.CreateContainer(opts) |
|
| 183 |
- if err != nil {
|
|
| 184 |
- return fmt.Errorf("unable to create build container: %v", err)
|
|
| 185 |
- } |
|
| 186 |
- e.Container = container |
|
| 187 |
- |
|
| 188 |
- // if we create the container, take responsibilty for cleaning up |
|
| 189 |
- defer e.Cleanup() |
|
| 190 |
- } |
|
| 191 |
- |
|
| 192 |
- // copy any source content into the temporary mount path |
|
| 193 |
- if mustStart && len(e.TransientMounts) > 0 {
|
|
| 194 |
- var copies []Copy |
|
| 195 |
- for i, mount := range e.TransientMounts {
|
|
| 196 |
- source := mount.SourcePath |
|
| 197 |
- copies = append(copies, Copy{
|
|
| 198 |
- Src: source, |
|
| 199 |
- Dest: []string{path.Join("/tmp/__temporarymount", strconv.Itoa(i))},
|
|
| 200 |
- }) |
|
| 201 |
- } |
|
| 202 |
- if err := e.Copy(copies...); err != nil {
|
|
| 203 |
- return fmt.Errorf("unable to copy build context into container: %v", err)
|
|
| 204 |
- } |
|
| 205 |
- } |
|
| 206 |
- |
|
| 207 |
- // TODO: lazy start |
|
| 208 |
- if mustStart && !e.Container.State.Running {
|
|
| 209 |
- var hostConfig docker.HostConfig |
|
| 210 |
- if e.HostConfig != nil {
|
|
| 211 |
- hostConfig = *e.HostConfig |
|
| 212 |
- } |
|
| 213 |
- |
|
| 214 |
- // mount individual items temporarily |
|
| 215 |
- for i, mount := range e.TransientMounts {
|
|
| 216 |
- if len(sharedMount) == 0 {
|
|
| 217 |
- return fmt.Errorf("no mount point available for temporary mounts")
|
|
| 218 |
- } |
|
| 219 |
- hostConfig.Binds = append( |
|
| 220 |
- hostConfig.Binds, |
|
| 221 |
- fmt.Sprintf("%s:%s:%s", path.Join(sharedMount, strconv.Itoa(i)), mount.DestinationPath, "ro"),
|
|
| 222 |
- ) |
|
| 223 |
- } |
|
| 224 |
- |
|
| 225 |
- if err := e.Client.StartContainer(e.Container.ID, &hostConfig); err != nil {
|
|
| 226 |
- return fmt.Errorf("unable to start build container: %v", err)
|
|
| 227 |
- } |
|
| 228 |
- // TODO: is this racy? may have to loop wait in the actual run step |
|
| 229 |
- } |
|
| 230 |
- |
|
| 231 |
- for _, child := range node.Children {
|
|
| 232 |
- step := b.Step() |
|
| 233 |
- if err := step.Resolve(child); err != nil {
|
|
| 234 |
- return err |
|
| 235 |
- } |
|
| 236 |
- glog.V(4).Infof("step: %s", step.Original)
|
|
| 237 |
- if e.LogFn != nil {
|
|
| 238 |
- e.LogFn(step.Original) |
|
| 239 |
- } |
|
| 240 |
- if err := b.Run(step, e); err != nil {
|
|
| 241 |
- return err |
|
| 242 |
- } |
|
| 243 |
- } |
|
| 244 |
- |
|
| 245 |
- if mustStart {
|
|
| 246 |
- glog.V(4).Infof("Stopping container %s ...", e.Container.ID)
|
|
| 247 |
- if err := e.Client.StopContainer(e.Container.ID, 0); err != nil {
|
|
| 248 |
- return fmt.Errorf("unable to stop build container: %v", err)
|
|
| 249 |
- } |
|
| 250 |
- } |
|
| 251 |
- |
|
| 252 |
- config := b.Config() |
|
| 253 |
- var repository, tag string |
|
| 254 |
- if len(e.Tag) > 0 {
|
|
| 255 |
- repository, tag = docker.ParseRepositoryTag(e.Tag) |
|
| 256 |
- glog.V(4).Infof("Committing built container %s as image %q: %#v", e.Container.ID, e.Tag, config)
|
|
| 257 |
- if e.LogFn != nil {
|
|
| 258 |
- e.LogFn("Committing changes to %s ...", e.Tag)
|
|
| 259 |
- } |
|
| 260 |
- } else {
|
|
| 261 |
- glog.V(4).Infof("Committing built container %s: %#v", e.Container.ID, config)
|
|
| 262 |
- if e.LogFn != nil {
|
|
| 263 |
- e.LogFn("Committing changes ...")
|
|
| 264 |
- } |
|
| 265 |
- } |
|
| 266 |
- |
|
| 267 |
- image, err := e.Client.CommitContainer(docker.CommitContainerOptions{
|
|
| 268 |
- Author: b.Author, |
|
| 269 |
- Container: e.Container.ID, |
|
| 270 |
- Run: config, |
|
| 271 |
- Repository: repository, |
|
| 272 |
- Tag: tag, |
|
| 273 |
- }) |
|
| 274 |
- if err != nil {
|
|
| 275 |
- return fmt.Errorf("unable to commit build container: %v", err)
|
|
| 276 |
- } |
|
| 277 |
- e.Image = image |
|
| 278 |
- glog.V(4).Infof("Committed %s to %s", e.Container.ID, e.Image.ID)
|
|
| 279 |
- if e.LogFn != nil {
|
|
| 280 |
- e.LogFn("Done")
|
|
| 281 |
- } |
|
| 282 |
- return nil |
|
| 283 |
-} |
|
| 284 |
- |
|
| 285 |
-// Cleanup will remove the container that created the build. |
|
| 286 |
-func (e *ClientExecutor) Cleanup() error {
|
|
| 287 |
- if e.Container == nil {
|
|
| 288 |
- return nil |
|
| 289 |
- } |
|
| 290 |
- err := e.Client.RemoveContainer(docker.RemoveContainerOptions{
|
|
| 291 |
- ID: e.Container.ID, |
|
| 292 |
- RemoveVolumes: true, |
|
| 293 |
- Force: true, |
|
| 294 |
- }) |
|
| 295 |
- if _, ok := err.(*docker.NoSuchContainer); err != nil && !ok {
|
|
| 296 |
- return fmt.Errorf("unable to cleanup build container: %v", err)
|
|
| 297 |
- } |
|
| 298 |
- e.Container = nil |
|
| 299 |
- return nil |
|
| 300 |
-} |
|
| 301 |
- |
|
| 302 |
-// CreateScratchImage creates a new, zero byte layer that is identical to "scratch" |
|
| 303 |
-// except that the resulting image will have two layers. |
|
| 304 |
-func (e *ClientExecutor) CreateScratchImage() (string, error) {
|
|
| 305 |
- random, err := randSeq(imageSafeCharacters, 24) |
|
| 306 |
- if err != nil {
|
|
| 307 |
- return "", err |
|
| 308 |
- } |
|
| 309 |
- name := fmt.Sprintf("scratch%s", random)
|
|
| 310 |
- |
|
| 311 |
- buf := &bytes.Buffer{}
|
|
| 312 |
- w := tar.NewWriter(buf) |
|
| 313 |
- w.Close() |
|
| 314 |
- |
|
| 315 |
- return name, e.Client.ImportImage(docker.ImportImageOptions{
|
|
| 316 |
- Repository: name, |
|
| 317 |
- Source: "-", |
|
| 318 |
- InputStream: buf, |
|
| 319 |
- }) |
|
| 320 |
-} |
|
| 321 |
- |
|
| 322 |
-// imageSafeCharacters are characters allowed to be part of a Docker image name. |
|
| 323 |
-const imageSafeCharacters = "abcdefghijklmnopqrstuvwxyz0123456789" |
|
| 324 |
- |
|
| 325 |
-// randSeq returns a sequence of random characters drawn from source. It returns |
|
| 326 |
-// an error if cryptographic randomness is not available or source is more than 255 |
|
| 327 |
-// characters. |
|
| 328 |
-func randSeq(source string, n int) (string, error) {
|
|
| 329 |
- if len(source) > 255 {
|
|
| 330 |
- return "", fmt.Errorf("source must be less than 256 bytes long")
|
|
| 331 |
- } |
|
| 332 |
- random := make([]byte, n) |
|
| 333 |
- if _, err := io.ReadFull(rand.Reader, random); err != nil {
|
|
| 334 |
- return "", err |
|
| 335 |
- } |
|
| 336 |
- for i := range random {
|
|
| 337 |
- random[i] = source[random[i]%byte(len(source))] |
|
| 338 |
- } |
|
| 339 |
- return string(random), nil |
|
| 340 |
-} |
|
| 341 |
- |
|
| 342 |
-// cleanupVolume attempts to remove the provided volume |
|
| 343 |
-func (e *ClientExecutor) cleanupVolume(name string) error {
|
|
| 344 |
- return e.Client.RemoveVolume(name) |
|
| 345 |
-} |
|
| 346 |
- |
|
| 347 |
-// CleanupImage attempts to remove the provided image. |
|
| 348 |
-func (e *ClientExecutor) CleanupImage(name string) error {
|
|
| 349 |
- return e.Client.RemoveImage(name) |
|
| 350 |
-} |
|
| 351 |
- |
|
| 352 |
-// LoadImage checks the client for an image matching from. If not found, |
|
| 353 |
-// attempts to pull the image and then tries to inspect again. |
|
| 354 |
-func (e *ClientExecutor) LoadImage(from string) (*docker.Image, error) {
|
|
| 355 |
- image, err := e.Client.InspectImage(from) |
|
| 356 |
- if err == nil {
|
|
| 357 |
- return image, nil |
|
| 358 |
- } |
|
| 359 |
- if err != docker.ErrNoSuchImage {
|
|
| 360 |
- return nil, err |
|
| 361 |
- } |
|
| 362 |
- |
|
| 363 |
- if !e.AllowPull {
|
|
| 364 |
- glog.V(4).Infof("image %s did not exist", from)
|
|
| 365 |
- return nil, docker.ErrNoSuchImage |
|
| 366 |
- } |
|
| 367 |
- |
|
| 368 |
- repository, tag := docker.ParseRepositoryTag(from) |
|
| 369 |
- if len(tag) == 0 {
|
|
| 370 |
- tag = "latest" |
|
| 371 |
- } |
|
| 372 |
- |
|
| 373 |
- glog.V(4).Infof("attempting to pull %s with auth from repository %s:%s", from, repository, tag)
|
|
| 374 |
- |
|
| 375 |
- // TODO: we may want to abstract looping over multiple credentials |
|
| 376 |
- auth, _ := e.AuthFn(repository) |
|
| 377 |
- if len(auth) == 0 {
|
|
| 378 |
- auth = append(auth, credentialprovider.LazyAuthConfiguration{})
|
|
| 379 |
- } |
|
| 380 |
- |
|
| 381 |
- if e.LogFn != nil {
|
|
| 382 |
- e.LogFn("Image %s was not found, pulling ...", from)
|
|
| 383 |
- } |
|
| 384 |
- |
|
| 385 |
- var lastErr error |
|
| 386 |
- outputProgress := func(s string) {
|
|
| 387 |
- e.LogFn("%s", s)
|
|
| 388 |
- } |
|
| 389 |
- for _, config := range auth {
|
|
| 390 |
- // TODO: handle IDs? |
|
| 391 |
- pullImageOptions := docker.PullImageOptions{
|
|
| 392 |
- Repository: repository, |
|
| 393 |
- Tag: tag, |
|
| 394 |
- OutputStream: imageprogress.NewPullWriter(outputProgress), |
|
| 395 |
- RawJSONStream: true, |
|
| 396 |
- } |
|
| 397 |
- if glog.V(5) {
|
|
| 398 |
- pullImageOptions.OutputStream = os.Stderr |
|
| 399 |
- pullImageOptions.RawJSONStream = false |
|
| 400 |
- } |
|
| 401 |
- authConfig := docker.AuthConfiguration{Username: config.Username, ServerAddress: config.ServerAddress, Password: config.Password}
|
|
| 402 |
- if err = e.Client.PullImage(pullImageOptions, authConfig); err == nil {
|
|
| 403 |
- break |
|
| 404 |
- } |
|
| 405 |
- lastErr = err |
|
| 406 |
- continue |
|
| 407 |
- } |
|
| 408 |
- if lastErr != nil {
|
|
| 409 |
- return nil, fmt.Errorf("unable to pull image (from: %s, tag: %s): %v", repository, tag, lastErr)
|
|
| 410 |
- } |
|
| 411 |
- |
|
| 412 |
- return e.Client.InspectImage(from) |
|
| 413 |
-} |
|
| 414 |
- |
|
| 415 |
-// Run executes a single Run command against the current container using exec(). |
|
| 416 |
-// Since exec does not allow ENV or WORKINGDIR to be set, we force the execution of |
|
| 417 |
-// the user command into a shell and perform those operations before. Since RUN |
|
| 418 |
-// requires /bin/sh, we can use both 'cd' and 'export'. |
|
| 419 |
-func (e *ClientExecutor) Run(run Run, config docker.Config) error {
|
|
| 420 |
- args := make([]string, len(run.Args)) |
|
| 421 |
- copy(args, run.Args) |
|
| 422 |
- |
|
| 423 |
- if runtime.GOOS == "windows" {
|
|
| 424 |
- if len(config.WorkingDir) > 0 {
|
|
| 425 |
- args[0] = fmt.Sprintf("cd %s && %s", bashQuote(config.WorkingDir), args[0])
|
|
| 426 |
- } |
|
| 427 |
- // TODO: implement windows ENV |
|
| 428 |
- args = append([]string{"cmd", "/S", "/C"}, args...)
|
|
| 429 |
- } else {
|
|
| 430 |
- if len(config.WorkingDir) > 0 {
|
|
| 431 |
- args[0] = fmt.Sprintf("cd %s && %s", bashQuote(config.WorkingDir), args[0])
|
|
| 432 |
- } |
|
| 433 |
- if len(config.Env) > 0 {
|
|
| 434 |
- args[0] = exportEnv(config.Env) + args[0] |
|
| 435 |
- } |
|
| 436 |
- args = append([]string{"/bin/sh", "-c"}, args...)
|
|
| 437 |
- } |
|
| 438 |
- |
|
| 439 |
- config.Cmd = args |
|
| 440 |
- |
|
| 441 |
- exec, err := e.Client.CreateExec(docker.CreateExecOptions{
|
|
| 442 |
- Cmd: config.Cmd, |
|
| 443 |
- Container: e.Container.ID, |
|
| 444 |
- AttachStdout: true, |
|
| 445 |
- AttachStderr: true, |
|
| 446 |
- User: config.User, |
|
| 447 |
- }) |
|
| 448 |
- if err != nil {
|
|
| 449 |
- return err |
|
| 450 |
- } |
|
| 451 |
- if err := e.Client.StartExec(exec.ID, docker.StartExecOptions{
|
|
| 452 |
- OutputStream: e.Out, |
|
| 453 |
- ErrorStream: e.ErrOut, |
|
| 454 |
- }); err != nil {
|
|
| 455 |
- return err |
|
| 456 |
- } |
|
| 457 |
- status, err := e.Client.InspectExec(exec.ID) |
|
| 458 |
- if err != nil {
|
|
| 459 |
- return err |
|
| 460 |
- } |
|
| 461 |
- if status.ExitCode != 0 {
|
|
| 462 |
- return fmt.Errorf("running '%s' failed with exit code %d", strings.Join(args, " "), status.ExitCode)
|
|
| 463 |
- } |
|
| 464 |
- return nil |
|
| 465 |
-} |
|
| 466 |
- |
|
| 467 |
-func (e *ClientExecutor) Copy(copies ...Copy) error {
|
|
| 468 |
- container := e.Container |
|
| 469 |
- for _, c := range copies {
|
|
| 470 |
- // TODO: reuse source |
|
| 471 |
- for _, dst := range c.Dest {
|
|
| 472 |
- glog.V(4).Infof("Archiving %s %t", c.Src, c.Download)
|
|
| 473 |
- r, closer, err := e.Archive(c.Src, dst, c.Download, c.Download) |
|
| 474 |
- if err != nil {
|
|
| 475 |
- return err |
|
| 476 |
- } |
|
| 477 |
- glog.V(5).Infof("Uploading to %s at %s", container.ID, dst)
|
|
| 478 |
- err = e.Client.UploadToContainer(container.ID, docker.UploadToContainerOptions{
|
|
| 479 |
- InputStream: r, |
|
| 480 |
- Path: "/", |
|
| 481 |
- }) |
|
| 482 |
- if err := closer.Close(); err != nil {
|
|
| 483 |
- glog.Errorf("Error while closing stream container copy stream %s: %v", container.ID, err)
|
|
| 484 |
- } |
|
| 485 |
- if err != nil {
|
|
| 486 |
- return err |
|
| 487 |
- } |
|
| 488 |
- } |
|
| 489 |
- } |
|
| 490 |
- return nil |
|
| 491 |
-} |
|
| 492 |
- |
|
| 493 |
-type closers []func() error |
|
| 494 |
- |
|
| 495 |
-func (c closers) Close() error {
|
|
| 496 |
- var lastErr error |
|
| 497 |
- for _, fn := range c {
|
|
| 498 |
- if err := fn(); err != nil {
|
|
| 499 |
- lastErr = err |
|
| 500 |
- } |
|
| 501 |
- } |
|
| 502 |
- return lastErr |
|
| 503 |
-} |
|
| 504 |
- |
|
| 505 |
-func (e *ClientExecutor) Archive(src, dst string, allowDecompression, allowDownload bool) (io.Reader, io.Closer, error) {
|
|
| 506 |
- var closer closers |
|
| 507 |
- var base string |
|
| 508 |
- var infos []CopyInfo |
|
| 509 |
- var err error |
|
| 510 |
- if isURL(src) {
|
|
| 511 |
- if !allowDownload {
|
|
| 512 |
- return nil, nil, fmt.Errorf("source can't be a URL")
|
|
| 513 |
- } |
|
| 514 |
- infos, base, err = DownloadURL(src, dst) |
|
| 515 |
- if len(base) > 0 {
|
|
| 516 |
- closer = append(closer, func() error { return os.RemoveAll(base) })
|
|
| 517 |
- } |
|
| 518 |
- } else {
|
|
| 519 |
- if filepath.IsAbs(src) {
|
|
| 520 |
- base = filepath.Dir(src) |
|
| 521 |
- src, err = filepath.Rel(base, src) |
|
| 522 |
- if err != nil {
|
|
| 523 |
- return nil, nil, err |
|
| 524 |
- } |
|
| 525 |
- } else {
|
|
| 526 |
- base = e.Directory |
|
| 527 |
- } |
|
| 528 |
- infos, err = CalcCopyInfo(src, base, allowDecompression, true) |
|
| 529 |
- } |
|
| 530 |
- if err != nil {
|
|
| 531 |
- closer.Close() |
|
| 532 |
- return nil, nil, err |
|
| 533 |
- } |
|
| 534 |
- |
|
| 535 |
- options := archiveOptionsFor(infos, dst, e.Excludes) |
|
| 536 |
- |
|
| 537 |
- glog.V(4).Infof("Tar of directory %s %#v", base, options)
|
|
| 538 |
- rc, err := archive.TarWithOptions(base, options) |
|
| 539 |
- closer = append(closer, rc.Close) |
|
| 540 |
- return rc, closer, err |
|
| 541 |
-} |
|
| 542 |
- |
|
| 543 |
-func archiveOptionsFor(infos []CopyInfo, dst string, excludes []string) *archive.TarOptions {
|
|
| 544 |
- dst = trimLeadingPath(dst) |
|
| 545 |
- patterns, patDirs, _, _ := fileutils.CleanPatterns(excludes) |
|
| 546 |
- options := &archive.TarOptions{}
|
|
| 547 |
- for _, info := range infos {
|
|
| 548 |
- if ok, _ := fileutils.OptimizedMatches(info.Path, patterns, patDirs); ok {
|
|
| 549 |
- continue |
|
| 550 |
- } |
|
| 551 |
- options.IncludeFiles = append(options.IncludeFiles, info.Path) |
|
| 552 |
- if len(dst) == 0 {
|
|
| 553 |
- continue |
|
| 554 |
- } |
|
| 555 |
- if options.RebaseNames == nil {
|
|
| 556 |
- options.RebaseNames = make(map[string]string) |
|
| 557 |
- } |
|
| 558 |
- if info.FromDir || strings.HasSuffix(dst, "/") || strings.HasSuffix(dst, "/.") || dst == "." {
|
|
| 559 |
- if strings.HasSuffix(info.Path, "/") {
|
|
| 560 |
- options.RebaseNames[info.Path] = dst |
|
| 561 |
- } else {
|
|
| 562 |
- options.RebaseNames[info.Path] = path.Join(dst, path.Base(info.Path)) |
|
| 563 |
- } |
|
| 564 |
- } else {
|
|
| 565 |
- options.RebaseNames[info.Path] = dst |
|
| 566 |
- } |
|
| 567 |
- } |
|
| 568 |
- options.ExcludePatterns = excludes |
|
| 569 |
- return options |
|
| 570 |
-} |
| 571 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,583 +0,0 @@ |
| 1 |
-// +build conformance |
|
| 2 |
- |
|
| 3 |
-package builder |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "archive/tar" |
|
| 7 |
- "bytes" |
|
| 8 |
- "flag" |
|
| 9 |
- "fmt" |
|
| 10 |
- "io" |
|
| 11 |
- "io/ioutil" |
|
| 12 |
- "os" |
|
| 13 |
- "os/exec" |
|
| 14 |
- "path/filepath" |
|
| 15 |
- "reflect" |
|
| 16 |
- "strings" |
|
| 17 |
- "testing" |
|
| 18 |
- "time" |
|
| 19 |
- |
|
| 20 |
- "github.com/docker/docker/builder/command" |
|
| 21 |
- "github.com/docker/docker/builder/parser" |
|
| 22 |
- docker "github.com/fsouza/go-dockerclient" |
|
| 23 |
- "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive" |
|
| 24 |
- "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils" |
|
| 25 |
- |
|
| 26 |
- "k8s.io/kubernetes/pkg/conversion" |
|
| 27 |
- "k8s.io/kubernetes/pkg/util/diff" |
|
| 28 |
-) |
|
| 29 |
- |
|
| 30 |
-var compareLayers = flag.Bool("compare-layers", false, "If true, compare each generated layer for equivalence")
|
|
| 31 |
- |
|
| 32 |
-type conformanceTest struct {
|
|
| 33 |
- Dockerfile string |
|
| 34 |
- Git string |
|
| 35 |
- ContextDir string |
|
| 36 |
- Ignore []ignoreFunc |
|
| 37 |
- PostClone func(dir string) error |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// TestConformance* compares the result of running the direct build against a |
|
| 41 |
-// sequential docker build. A dockerfile and git repo is loaded, then each step |
|
| 42 |
-// in the file is run sequentially, committing after each step. The generated |
|
| 43 |
-// image.Config and the resulting filesystems are compared. The next step reuses |
|
| 44 |
-// the previously generated layer and performs an incremental diff. This ensures |
|
| 45 |
-// that each step is functionally equivalent. |
|
| 46 |
-// |
|
| 47 |
-// Deviations: |
|
| 48 |
-// * Builds run at different times |
|
| 49 |
-// * Modification timestamps are ignored on files |
|
| 50 |
-// * Some processes (gem install) result in files created in the image that |
|
| 51 |
-// have different content because of that (timestamps in files). We treat |
|
| 52 |
-// a file that is identical except for size within 10 bytes and neither old |
|
| 53 |
-// or new is zero bytes to be identical. |
|
| 54 |
-// * Docker container commit with ENV FOO=BAR and a Docker build with line |
|
| 55 |
-// ENV FOO=BAR will generate an image with FOO=BAR in different positions |
|
| 56 |
-// (commit places the variable first, build: last). We try to align the |
|
| 57 |
-// generated environment variable to ensure they are equal. |
|
| 58 |
-// * The parent image ID is ignored. |
|
| 59 |
-// |
|
| 60 |
-// TODO: .dockerignore |
|
| 61 |
-// TODO: check context dir |
|
| 62 |
-// TODO: ONBUILD |
|
| 63 |
-// TODO: ensure that the final built image has the right UIDs |
|
| 64 |
-// |
|
| 65 |
-func TestConformanceInternal(t *testing.T) {
|
|
| 66 |
- testCases := []conformanceTest{
|
|
| 67 |
- {
|
|
| 68 |
- ContextDir: "testdata/dir", |
|
| 69 |
- }, |
|
| 70 |
- // TODO: Fix this test |
|
| 71 |
- // {
|
|
| 72 |
- // ContextDir: "testdata/ignore", |
|
| 73 |
- // }, |
|
| 74 |
- {
|
|
| 75 |
- Dockerfile: "testdata/Dockerfile.env", |
|
| 76 |
- }, |
|
| 77 |
- {
|
|
| 78 |
- Dockerfile: "testdata/Dockerfile.edgecases", |
|
| 79 |
- }, |
|
| 80 |
- {
|
|
| 81 |
- Dockerfile: "testdata/Dockerfile.exposedefault", |
|
| 82 |
- }, |
|
| 83 |
- {
|
|
| 84 |
- Dockerfile: "testdata/Dockerfile.add", |
|
| 85 |
- }, |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- c, err := docker.NewClientFromEnv() |
|
| 89 |
- if err != nil {
|
|
| 90 |
- t.Fatal(err) |
|
| 91 |
- } |
|
| 92 |
- |
|
| 93 |
- for i, test := range testCases {
|
|
| 94 |
- conformanceTester(t, c, test, i, *compareLayers) |
|
| 95 |
- } |
|
| 96 |
-} |
|
| 97 |
- |
|
| 98 |
-// TestConformanceExternal applies external repo testing that may be more expensive or |
|
| 99 |
-// change more frequently. |
|
| 100 |
-func TestConformanceExternal(t *testing.T) {
|
|
| 101 |
- testCases := []conformanceTest{
|
|
| 102 |
- {
|
|
| 103 |
- // Tests user ownership change under COPY |
|
| 104 |
- Git: "https://github.com/openshift/ruby-hello-world.git", |
|
| 105 |
- }, |
|
| 106 |
- {
|
|
| 107 |
- // Tests Non-default location dockerfile |
|
| 108 |
- Dockerfile: "Dockerfile.build", |
|
| 109 |
- Git: "https://github.com/docker-library/hello-world.git", |
|
| 110 |
- PostClone: func(dir string) error {
|
|
| 111 |
- return os.Remove(filepath.Join(dir, ".dockerignore")) |
|
| 112 |
- }, |
|
| 113 |
- }, |
|
| 114 |
- {
|
|
| 115 |
- // Tests COPY and other complex interactions of ENV |
|
| 116 |
- ContextDir: "9.3", |
|
| 117 |
- Dockerfile: "9.3/Dockerfile", |
|
| 118 |
- Git: "https://github.com/docker-library/postgres.git", |
|
| 119 |
- Ignore: []ignoreFunc{
|
|
| 120 |
- func(a, b *tar.Header) bool {
|
|
| 121 |
- switch {
|
|
| 122 |
- case (a != nil) == (b != nil): |
|
| 123 |
- return false |
|
| 124 |
- case a != nil: |
|
| 125 |
- return strings.HasPrefix(a.Name, "etc/ssl/certs/") |
|
| 126 |
- case b != nil: |
|
| 127 |
- return strings.HasPrefix(b.Name, "etc/ssl/certs/") |
|
| 128 |
- default: |
|
| 129 |
- return false |
|
| 130 |
- } |
|
| 131 |
- }, |
|
| 132 |
- }, |
|
| 133 |
- }, |
|
| 134 |
- } |
|
| 135 |
- |
|
| 136 |
- c, err := docker.NewClientFromEnv() |
|
| 137 |
- if err != nil {
|
|
| 138 |
- t.Fatal(err) |
|
| 139 |
- } |
|
| 140 |
- |
|
| 141 |
- for i, test := range testCases {
|
|
| 142 |
- conformanceTester(t, c, test, i, *compareLayers) |
|
| 143 |
- } |
|
| 144 |
-} |
|
| 145 |
- |
|
| 146 |
-func conformanceTester(t *testing.T, c *docker.Client, test conformanceTest, i int, deep bool) {
|
|
| 147 |
- dockerfile := test.Dockerfile |
|
| 148 |
- if len(dockerfile) == 0 {
|
|
| 149 |
- dockerfile = "Dockerfile" |
|
| 150 |
- } |
|
| 151 |
- tmpDir, err := ioutil.TempDir("", "dockerbuild-conformance-")
|
|
| 152 |
- if err != nil {
|
|
| 153 |
- t.Fatal(err) |
|
| 154 |
- } |
|
| 155 |
- defer os.RemoveAll(tmpDir) |
|
| 156 |
- |
|
| 157 |
- dir := tmpDir |
|
| 158 |
- contextDir := filepath.Join(dir, test.ContextDir) |
|
| 159 |
- dockerfilePath := filepath.Join(dir, dockerfile) |
|
| 160 |
- |
|
| 161 |
- // clone repo or copy the Dockerfile |
|
| 162 |
- var input string |
|
| 163 |
- switch {
|
|
| 164 |
- case len(test.Git) > 0: |
|
| 165 |
- input = test.Git |
|
| 166 |
- cmd := exec.Command("git", "clone", test.Git, dir)
|
|
| 167 |
- out, err := cmd.CombinedOutput() |
|
| 168 |
- if err != nil {
|
|
| 169 |
- t.Errorf("unable to clone %q: %v\n%s", test.Git, err, out)
|
|
| 170 |
- return |
|
| 171 |
- } |
|
| 172 |
- |
|
| 173 |
- if test.PostClone != nil {
|
|
| 174 |
- if err := test.PostClone(dir); err != nil {
|
|
| 175 |
- t.Errorf("unable to fixup clone: %v", err)
|
|
| 176 |
- return |
|
| 177 |
- } |
|
| 178 |
- } |
|
| 179 |
- |
|
| 180 |
- case len(test.Dockerfile) > 0: |
|
| 181 |
- input = dockerfile |
|
| 182 |
- dockerfilePath = filepath.Join(dir, "Dockerfile") |
|
| 183 |
- if _, err := fileutils.CopyFile(filepath.Join("", dockerfile), dockerfilePath); err != nil {
|
|
| 184 |
- t.Fatal(err) |
|
| 185 |
- } |
|
| 186 |
- dockerfile = "Dockerfile" |
|
| 187 |
- |
|
| 188 |
- default: |
|
| 189 |
- input = filepath.Join(test.ContextDir, dockerfile) |
|
| 190 |
- dockerfilePath = input |
|
| 191 |
- contextDir = test.ContextDir |
|
| 192 |
- dir = test.ContextDir |
|
| 193 |
- } |
|
| 194 |
- |
|
| 195 |
- // read the dockerfile |
|
| 196 |
- data, err := ioutil.ReadFile(dockerfilePath) |
|
| 197 |
- if err != nil {
|
|
| 198 |
- t.Errorf("%d: unable to read Dockerfile %q: %v", i, input, err)
|
|
| 199 |
- return |
|
| 200 |
- } |
|
| 201 |
- node, err := parser.Parse(bytes.NewBuffer(data)) |
|
| 202 |
- if err != nil {
|
|
| 203 |
- t.Errorf("%d: can't parse Dockerfile %q: %v", i, input, err)
|
|
| 204 |
- return |
|
| 205 |
- } |
|
| 206 |
- from, err := NewBuilder().From(node) |
|
| 207 |
- if err != nil {
|
|
| 208 |
- t.Errorf("%d: can't get base FROM %q: %v", i, input, err)
|
|
| 209 |
- return |
|
| 210 |
- } |
|
| 211 |
- nameFormat := "conformance-dockerbuild-%d-%s-%d" |
|
| 212 |
- |
|
| 213 |
- var toDelete []string |
|
| 214 |
- steps := node.Children |
|
| 215 |
- lastImage := from |
|
| 216 |
- |
|
| 217 |
- ignoreSmallFileChange := func(a, b *tar.Header) bool {
|
|
| 218 |
- if a == nil || b == nil {
|
|
| 219 |
- return false |
|
| 220 |
- } |
|
| 221 |
- diff := a.Size - b.Size |
|
| 222 |
- if differOnlyByFileSize(a, b, 10) {
|
|
| 223 |
- t.Logf("WARNING: %s differs only in size by %d bytes, probably a timestamp value change", a.Name, diff)
|
|
| 224 |
- return true |
|
| 225 |
- } |
|
| 226 |
- return false |
|
| 227 |
- } |
|
| 228 |
- |
|
| 229 |
- if deep {
|
|
| 230 |
- // execute each step on both Docker build and the direct builder, comparing as we |
|
| 231 |
- // go |
|
| 232 |
- fail := false |
|
| 233 |
- for j := range steps {
|
|
| 234 |
- testFile := dockerfileWithFrom(lastImage, steps[j:j+1]) |
|
| 235 |
- |
|
| 236 |
- nameDirect := fmt.Sprintf(nameFormat, i, "direct", j) |
|
| 237 |
- nameDocker := fmt.Sprintf(nameFormat, i, "docker", j) |
|
| 238 |
- |
|
| 239 |
- // run docker build |
|
| 240 |
- if err := ioutil.WriteFile(dockerfilePath, []byte(testFile), 0600); err != nil {
|
|
| 241 |
- t.Errorf("%d: unable to update Dockerfile %q: %v", i, dockerfilePath, err)
|
|
| 242 |
- break |
|
| 243 |
- } |
|
| 244 |
- in, err := archive.TarWithOptions(dir, &archive.TarOptions{IncludeFiles: []string{"."}})
|
|
| 245 |
- if err != nil {
|
|
| 246 |
- t.Errorf("%d: unable to generate build context %q: %v", i, dockerfilePath, err)
|
|
| 247 |
- break |
|
| 248 |
- } |
|
| 249 |
- out := &bytes.Buffer{}
|
|
| 250 |
- if err := c.BuildImage(docker.BuildImageOptions{
|
|
| 251 |
- Name: nameDocker, |
|
| 252 |
- Dockerfile: dockerfile, |
|
| 253 |
- RmTmpContainer: true, |
|
| 254 |
- ForceRmTmpContainer: true, |
|
| 255 |
- InputStream: in, |
|
| 256 |
- OutputStream: out, |
|
| 257 |
- }); err != nil {
|
|
| 258 |
- in.Close() |
|
| 259 |
- t.Errorf("%d: unable to build Docker image %q: %v\n%s", i, test.Git, err, out)
|
|
| 260 |
- break |
|
| 261 |
- } |
|
| 262 |
- toDelete = append(toDelete, nameDocker) |
|
| 263 |
- |
|
| 264 |
- // run direct build |
|
| 265 |
- e := NewClientExecutor(c) |
|
| 266 |
- out = &bytes.Buffer{}
|
|
| 267 |
- e.Out, e.ErrOut = out, out |
|
| 268 |
- e.Directory = contextDir |
|
| 269 |
- e.Tag = nameDirect |
|
| 270 |
- if err := e.Build(bytes.NewBufferString(testFile), nil); err != nil {
|
|
| 271 |
- t.Errorf("%d: failed to build step %d in dockerfile %q: %s\n%s", i, j, dockerfilePath, steps[j].Original, out)
|
|
| 272 |
- break |
|
| 273 |
- } |
|
| 274 |
- toDelete = append(toDelete, nameDirect) |
|
| 275 |
- |
|
| 276 |
- // only compare filesystem on layers that change the filesystem |
|
| 277 |
- mutation := steps[j].Value == command.Add || steps[j].Value == command.Copy || steps[j].Value == command.Run |
|
| 278 |
- // metadata must be strictly equal |
|
| 279 |
- if !equivalentImages( |
|
| 280 |
- t, c, nameDocker, nameDirect, mutation, |
|
| 281 |
- metadataEqual, |
|
| 282 |
- append(ignoreFuncs{ignoreSmallFileChange}, test.Ignore...)...,
|
|
| 283 |
- ) {
|
|
| 284 |
- t.Errorf("%d: layered Docker build was not equivalent to direct layer image metadata %s", i, input)
|
|
| 285 |
- fail = true |
|
| 286 |
- } |
|
| 287 |
- |
|
| 288 |
- lastImage = nameDocker |
|
| 289 |
- } |
|
| 290 |
- |
|
| 291 |
- if fail {
|
|
| 292 |
- t.Fatalf("%d: Conformance test failed for %s", i, input)
|
|
| 293 |
- } |
|
| 294 |
- |
|
| 295 |
- } else {
|
|
| 296 |
- exclude, _ := ParseDockerignore(dir) |
|
| 297 |
- //exclude = append(filtered, ".dockerignore") |
|
| 298 |
- in, err := archive.TarWithOptions(dir, &archive.TarOptions{IncludeFiles: []string{"."}, ExcludePatterns: exclude})
|
|
| 299 |
- if err != nil {
|
|
| 300 |
- t.Errorf("%d: unable to generate build context %q: %v", i, dockerfilePath, err)
|
|
| 301 |
- return |
|
| 302 |
- } |
|
| 303 |
- out := &bytes.Buffer{}
|
|
| 304 |
- nameDocker := fmt.Sprintf(nameFormat, i, "docker", 0) |
|
| 305 |
- if err := c.BuildImage(docker.BuildImageOptions{
|
|
| 306 |
- Name: nameDocker, |
|
| 307 |
- Dockerfile: dockerfile, |
|
| 308 |
- RmTmpContainer: true, |
|
| 309 |
- ForceRmTmpContainer: true, |
|
| 310 |
- InputStream: in, |
|
| 311 |
- OutputStream: out, |
|
| 312 |
- }); err != nil {
|
|
| 313 |
- in.Close() |
|
| 314 |
- t.Errorf("%d: unable to build Docker image %q: %v\n%s", i, test.Git, err, out)
|
|
| 315 |
- return |
|
| 316 |
- } |
|
| 317 |
- lastImage = nameDocker |
|
| 318 |
- toDelete = append(toDelete, nameDocker) |
|
| 319 |
- } |
|
| 320 |
- |
|
| 321 |
- // if we ran more than one step, compare the squashed output with the docker build output |
|
| 322 |
- if len(steps) > 1 || !deep {
|
|
| 323 |
- nameDirect := fmt.Sprintf(nameFormat, i, "direct", len(steps)-1) |
|
| 324 |
- e := NewClientExecutor(c) |
|
| 325 |
- out := &bytes.Buffer{}
|
|
| 326 |
- e.Out, e.ErrOut = out, out |
|
| 327 |
- e.Directory = contextDir |
|
| 328 |
- e.Tag = nameDirect |
|
| 329 |
- if err := e.Build(bytes.NewBuffer(data), nil); err != nil {
|
|
| 330 |
- t.Errorf("%d: failed to build complete image in %q: %v\n%s", i, input, err, out)
|
|
| 331 |
- } else {
|
|
| 332 |
- if !equivalentImages( |
|
| 333 |
- t, c, lastImage, nameDirect, true, |
|
| 334 |
- // metadata should be loosely equivalent, but because we squash and because of limitations |
|
| 335 |
- // in docker commit, there are some differences |
|
| 336 |
- metadataLayerEquivalent, |
|
| 337 |
- append(ignoreFuncs{
|
|
| 338 |
- ignoreSmallFileChange, |
|
| 339 |
- // the direct dockerfile contains all steps, the layered image is synthetic from our previous |
|
| 340 |
- // test and so only contains the last layer |
|
| 341 |
- ignoreDockerfileSize(dockerfile), |
|
| 342 |
- }, test.Ignore...)..., |
|
| 343 |
- ) {
|
|
| 344 |
- t.Errorf("%d: full Docker build was not equivalent to squashed image metadata %s", i, input)
|
|
| 345 |
- } |
|
| 346 |
- } |
|
| 347 |
- } |
|
| 348 |
- |
|
| 349 |
- for _, s := range toDelete {
|
|
| 350 |
- c.RemoveImageExtended(s, docker.RemoveImageOptions{Force: true})
|
|
| 351 |
- } |
|
| 352 |
-} |
|
| 353 |
- |
|
| 354 |
-// ignoreFunc returns true if the difference between the two can be ignored |
|
| 355 |
-type ignoreFunc func(a, b *tar.Header) bool |
|
| 356 |
- |
|
| 357 |
-type ignoreFuncs []ignoreFunc |
|
| 358 |
- |
|
| 359 |
-func (fns ignoreFuncs) Ignore(a, b *tar.Header) bool {
|
|
| 360 |
- for _, fn := range fns {
|
|
| 361 |
- if fn(a, b) {
|
|
| 362 |
- return true |
|
| 363 |
- } |
|
| 364 |
- } |
|
| 365 |
- return false |
|
| 366 |
-} |
|
| 367 |
- |
|
| 368 |
-// metadataFunc returns true if the metadata is equivalent |
|
| 369 |
-type metadataFunc func(a, b *docker.Config) bool |
|
| 370 |
- |
|
| 371 |
-// metadataEqual checks that the metadata of two images is directly equivalent. |
|
| 372 |
-func metadataEqual(a, b *docker.Config) bool {
|
|
| 373 |
- // compare output metadata |
|
| 374 |
- a.Image, b.Image = "", "" |
|
| 375 |
- e1, e2 := envMap(a.Env), envMap(b.Env) |
|
| 376 |
- if !conversion.EqualitiesOrDie().DeepEqual(e1, e2) {
|
|
| 377 |
- return false |
|
| 378 |
- } |
|
| 379 |
- a.Env, b.Env = nil, nil |
|
| 380 |
- if !conversion.EqualitiesOrDie().DeepEqual(a, b) {
|
|
| 381 |
- return false |
|
| 382 |
- } |
|
| 383 |
- return true |
|
| 384 |
-} |
|
| 385 |
- |
|
| 386 |
-// metadataLayerEquivalent returns true if the last layer of a is equivalent to b, assuming |
|
| 387 |
-// that b is squashed over multiple layers, and a is not. b, for instance, will have an empty |
|
| 388 |
-// slice entrypoint, while a would have a nil entrypoint. |
|
| 389 |
-func metadataLayerEquivalent(a, b *docker.Config) bool {
|
|
| 390 |
- if a.Entrypoint == nil && len(b.Entrypoint) == 0 {
|
|
| 391 |
- // we are forced to set Entrypoint [] to reset the entrypoint |
|
| 392 |
- b.Entrypoint = nil |
|
| 393 |
- } |
|
| 394 |
- if len(a.OnBuild) == 1 && len(b.OnBuild) > 0 && a.OnBuild[0] == b.OnBuild[len(b.OnBuild)-1] {
|
|
| 395 |
- // a layered file will only contain the last OnBuild statement |
|
| 396 |
- b.OnBuild = a.OnBuild |
|
| 397 |
- } |
|
| 398 |
- return metadataEqual(a, b) |
|
| 399 |
-} |
|
| 400 |
- |
|
| 401 |
-// equivalentImages executes the provided checks against two docker images, returning true |
|
| 402 |
-// if the images are equivalent, and recording a test suite error in any other condition. |
|
| 403 |
-func equivalentImages(t *testing.T, c *docker.Client, a, b string, testFilesystem bool, metadataFn metadataFunc, ignoreFns ...ignoreFunc) bool {
|
|
| 404 |
- imageA, err := c.InspectImage(a) |
|
| 405 |
- if err != nil {
|
|
| 406 |
- t.Errorf("can't get image %q: %v", a, err)
|
|
| 407 |
- return false |
|
| 408 |
- } |
|
| 409 |
- imageB, err := c.InspectImage(b) |
|
| 410 |
- if err != nil {
|
|
| 411 |
- t.Errorf("can't get image %q: %v", b, err)
|
|
| 412 |
- return false |
|
| 413 |
- } |
|
| 414 |
- |
|
| 415 |
- if !metadataFn(imageA.Config, imageB.Config) {
|
|
| 416 |
- t.Errorf("generated image metadata did not match: %s", diff.ObjectDiff(imageA.Config, imageB.Config))
|
|
| 417 |
- return false |
|
| 418 |
- } |
|
| 419 |
- |
|
| 420 |
- // for mutation commands, check the layer diff |
|
| 421 |
- if testFilesystem {
|
|
| 422 |
- differs, onlyA, onlyB, err := compareImageFS(c, a, b) |
|
| 423 |
- if err != nil {
|
|
| 424 |
- t.Errorf("can't calculate FS differences %q: %v", a, err)
|
|
| 425 |
- return false |
|
| 426 |
- } |
|
| 427 |
- for k, v := range differs {
|
|
| 428 |
- if ignoreFuncs(ignoreFns).Ignore(v[0], v[1]) {
|
|
| 429 |
- delete(differs, k) |
|
| 430 |
- continue |
|
| 431 |
- } |
|
| 432 |
- t.Errorf("%s %s differs: %s", a, k, diff.ObjectDiff(v[0], v[1]))
|
|
| 433 |
- } |
|
| 434 |
- for k, v := range onlyA {
|
|
| 435 |
- if ignoreFuncs(ignoreFns).Ignore(v, nil) {
|
|
| 436 |
- delete(onlyA, k) |
|
| 437 |
- continue |
|
| 438 |
- } |
|
| 439 |
- } |
|
| 440 |
- for k, v := range onlyB {
|
|
| 441 |
- if ignoreFuncs(ignoreFns).Ignore(nil, v) {
|
|
| 442 |
- delete(onlyB, k) |
|
| 443 |
- continue |
|
| 444 |
- } |
|
| 445 |
- } |
|
| 446 |
- if len(onlyA)+len(onlyB)+len(differs) > 0 {
|
|
| 447 |
- t.Errorf("a=%v b=%v diff=%v", onlyA, onlyB, differs)
|
|
| 448 |
- return false |
|
| 449 |
- } |
|
| 450 |
- } |
|
| 451 |
- return true |
|
| 452 |
-} |
|
| 453 |
- |
|
| 454 |
-// dockerfileWithFrom returns the contents of a new docker file with a different |
|
| 455 |
-// FROM as the first line. |
|
| 456 |
-func dockerfileWithFrom(from string, steps []*parser.Node) string {
|
|
| 457 |
- lines := []string{}
|
|
| 458 |
- lines = append(lines, fmt.Sprintf("FROM %s", from))
|
|
| 459 |
- for _, step := range steps {
|
|
| 460 |
- lines = append(lines, step.Original) |
|
| 461 |
- } |
|
| 462 |
- return strings.Join(lines, "\n") |
|
| 463 |
-} |
|
| 464 |
- |
|
| 465 |
-// envMap returns a map from a list of environment variables. |
|
| 466 |
-func envMap(env []string) map[string]string {
|
|
| 467 |
- out := make(map[string]string) |
|
| 468 |
- for _, envVar := range env {
|
|
| 469 |
- parts := strings.SplitN(envVar, "=", 2) |
|
| 470 |
- if len(parts) != 2 {
|
|
| 471 |
- out[envVar] = "" |
|
| 472 |
- continue |
|
| 473 |
- } |
|
| 474 |
- out[parts[0]] = parts[1] |
|
| 475 |
- } |
|
| 476 |
- return out |
|
| 477 |
-} |
|
| 478 |
- |
|
| 479 |
-// differOnlyByFileSize returns true iff the headers differ only by size, but |
|
| 480 |
-// that differences is less than within bytes. |
|
| 481 |
-func differOnlyByFileSize(a, b *tar.Header, within int64) bool {
|
|
| 482 |
- if a == nil || b == nil {
|
|
| 483 |
- return false |
|
| 484 |
- } |
|
| 485 |
- if a.Size == b.Size {
|
|
| 486 |
- return false |
|
| 487 |
- } |
|
| 488 |
- |
|
| 489 |
- diff := a.Size - b.Size |
|
| 490 |
- if diff < 0 {
|
|
| 491 |
- diff = diff * -1 |
|
| 492 |
- } |
|
| 493 |
- if diff < within && a.Size != 0 && b.Size != 0 {
|
|
| 494 |
- a.Size = b.Size |
|
| 495 |
- if reflect.DeepEqual(a, b) {
|
|
| 496 |
- return true |
|
| 497 |
- } |
|
| 498 |
- } |
|
| 499 |
- return false |
|
| 500 |
-} |
|
| 501 |
- |
|
| 502 |
-// ignore Dockerfile being different, artifact of this test |
|
| 503 |
-func ignoreDockerfileSize(dockerfile string) ignoreFunc {
|
|
| 504 |
- return func(a, b *tar.Header) bool {
|
|
| 505 |
- if a == nil || b == nil {
|
|
| 506 |
- return false |
|
| 507 |
- } |
|
| 508 |
- if !strings.HasSuffix(a.Name, dockerfile) {
|
|
| 509 |
- return false |
|
| 510 |
- } |
|
| 511 |
- if a.Size != b.Size {
|
|
| 512 |
- a.Size = b.Size |
|
| 513 |
- return reflect.DeepEqual(a, b) |
|
| 514 |
- } |
|
| 515 |
- return false |
|
| 516 |
- } |
|
| 517 |
-} |
|
| 518 |
- |
|
| 519 |
-// compareImageFS exports the file systems of two images and returns a map |
|
| 520 |
-// of files that differ in any way (modification time excluded), only exist in |
|
| 521 |
-// image A, or only existing in image B. |
|
| 522 |
-func compareImageFS(c *docker.Client, a, b string) (differ map[string][]*tar.Header, onlyA, onlyB map[string]*tar.Header, err error) {
|
|
| 523 |
- fsA, err := imageFSMetadata(c, a) |
|
| 524 |
- if err != nil {
|
|
| 525 |
- return nil, nil, nil, err |
|
| 526 |
- } |
|
| 527 |
- fsB, err := imageFSMetadata(c, b) |
|
| 528 |
- if err != nil {
|
|
| 529 |
- return nil, nil, nil, err |
|
| 530 |
- } |
|
| 531 |
- differ = make(map[string][]*tar.Header) |
|
| 532 |
- onlyA = make(map[string]*tar.Header) |
|
| 533 |
- onlyB = fsB |
|
| 534 |
- for k, v1 := range fsA {
|
|
| 535 |
- v2, ok := fsB[k] |
|
| 536 |
- if !ok {
|
|
| 537 |
- onlyA[k] = v1 |
|
| 538 |
- continue |
|
| 539 |
- } |
|
| 540 |
- delete(onlyB, k) |
|
| 541 |
- // we ignore modification time differences |
|
| 542 |
- v1.ModTime = time.Time{}
|
|
| 543 |
- v2.ModTime = time.Time{}
|
|
| 544 |
- if !reflect.DeepEqual(v1, v2) {
|
|
| 545 |
- differ[k] = []*tar.Header{v1, v2}
|
|
| 546 |
- } |
|
| 547 |
- } |
|
| 548 |
- return differ, onlyA, onlyB, nil |
|
| 549 |
-} |
|
| 550 |
- |
|
| 551 |
-// imageFSMetadata creates a container and reads the filesystem metadata out of the archive. |
|
| 552 |
-func imageFSMetadata(c *docker.Client, name string) (map[string]*tar.Header, error) {
|
|
| 553 |
- container, err := c.CreateContainer(docker.CreateContainerOptions{Name: name + "-export", Config: &docker.Config{Image: name}})
|
|
| 554 |
- if err != nil {
|
|
| 555 |
- return nil, err |
|
| 556 |
- } |
|
| 557 |
- defer c.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID, RemoveVolumes: true, Force: true})
|
|
| 558 |
- |
|
| 559 |
- ch := make(chan struct{})
|
|
| 560 |
- result := make(map[string]*tar.Header) |
|
| 561 |
- r, w := io.Pipe() |
|
| 562 |
- go func() {
|
|
| 563 |
- defer close(ch) |
|
| 564 |
- out := tar.NewReader(r) |
|
| 565 |
- for {
|
|
| 566 |
- h, err := out.Next() |
|
| 567 |
- if err != nil {
|
|
| 568 |
- if err == io.EOF {
|
|
| 569 |
- w.Close() |
|
| 570 |
- } else {
|
|
| 571 |
- w.CloseWithError(err) |
|
| 572 |
- } |
|
| 573 |
- break |
|
| 574 |
- } |
|
| 575 |
- result[h.Name] = h |
|
| 576 |
- } |
|
| 577 |
- }() |
|
| 578 |
- if err := c.ExportContainer(docker.ExportContainerOptions{ID: container.ID, OutputStream: w}); err != nil {
|
|
| 579 |
- return nil, err |
|
| 580 |
- } |
|
| 581 |
- <-ch |
|
| 582 |
- return result, nil |
|
| 583 |
-} |
| 584 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,13 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-const ( |
|
| 4 |
- // in docker/system |
|
| 5 |
- NoBaseImageSpecifier = "scratch" |
|
| 6 |
- |
|
| 7 |
- // not yet part of our import |
|
| 8 |
- commandArg = "arg" |
|
| 9 |
- commandStopSignal = "stopsignal" |
|
| 10 |
- |
|
| 11 |
- // in docker/system |
|
| 12 |
- defaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" |
|
| 13 |
-) |
| 14 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,152 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io" |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "net/http" |
|
| 8 |
- "net/url" |
|
| 9 |
- "os" |
|
| 10 |
- "path" |
|
| 11 |
- "path/filepath" |
|
| 12 |
- "strings" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-type CopyInfo struct {
|
|
| 16 |
- os.FileInfo |
|
| 17 |
- Path string |
|
| 18 |
- Decompress bool |
|
| 19 |
- FromDir bool |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-// CalcCopyInfo identifies the source files selected by a Dockerfile ADD or COPY instruction. |
|
| 23 |
-func CalcCopyInfo(origPath, rootPath string, allowLocalDecompression, allowWildcards bool) ([]CopyInfo, error) {
|
|
| 24 |
- origPath = trimLeadingPath(origPath) |
|
| 25 |
- // Deal with wildcards |
|
| 26 |
- if allowWildcards && containsWildcards(origPath) {
|
|
| 27 |
- matchPath := filepath.Join(rootPath, origPath) |
|
| 28 |
- var copyInfos []CopyInfo |
|
| 29 |
- if err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
|
|
| 30 |
- if err != nil {
|
|
| 31 |
- return err |
|
| 32 |
- } |
|
| 33 |
- if info.Name() == "" {
|
|
| 34 |
- // Why are we doing this check? |
|
| 35 |
- return nil |
|
| 36 |
- } |
|
| 37 |
- if match, _ := filepath.Match(matchPath, path); !match {
|
|
| 38 |
- return nil |
|
| 39 |
- } |
|
| 40 |
- |
|
| 41 |
- // Note we set allowWildcards to false in case the name has |
|
| 42 |
- // a * in it |
|
| 43 |
- subInfos, err := CalcCopyInfo(trimLeadingPath(strings.TrimPrefix(path, rootPath)), rootPath, allowLocalDecompression, false) |
|
| 44 |
- if err != nil {
|
|
| 45 |
- return err |
|
| 46 |
- } |
|
| 47 |
- copyInfos = append(copyInfos, subInfos...) |
|
| 48 |
- return nil |
|
| 49 |
- }); err != nil {
|
|
| 50 |
- return nil, err |
|
| 51 |
- } |
|
| 52 |
- return copyInfos, nil |
|
| 53 |
- } |
|
| 54 |
- |
|
| 55 |
- // flatten the root directory so we can rebase it |
|
| 56 |
- if origPath == "." {
|
|
| 57 |
- var copyInfos []CopyInfo |
|
| 58 |
- infos, err := ioutil.ReadDir(rootPath) |
|
| 59 |
- if err != nil {
|
|
| 60 |
- return nil, err |
|
| 61 |
- } |
|
| 62 |
- for _, info := range infos {
|
|
| 63 |
- copyInfos = append(copyInfos, CopyInfo{FileInfo: info, Path: info.Name(), Decompress: allowLocalDecompression, FromDir: true})
|
|
| 64 |
- } |
|
| 65 |
- return copyInfos, nil |
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- // Must be a dir or a file |
|
| 69 |
- fi, err := os.Stat(filepath.Join(rootPath, origPath)) |
|
| 70 |
- if err != nil {
|
|
| 71 |
- return nil, err |
|
| 72 |
- } |
|
| 73 |
- |
|
| 74 |
- origPath = trimTrailingDot(origPath) |
|
| 75 |
- return []CopyInfo{{FileInfo: fi, Path: origPath, Decompress: allowLocalDecompression}}, nil
|
|
| 76 |
-} |
|
| 77 |
- |
|
| 78 |
-func DownloadURL(src, dst string) ([]CopyInfo, string, error) {
|
|
| 79 |
- // get filename from URL |
|
| 80 |
- u, err := url.Parse(src) |
|
| 81 |
- if err != nil {
|
|
| 82 |
- return nil, "", err |
|
| 83 |
- } |
|
| 84 |
- base := path.Base(u.Path) |
|
| 85 |
- if base == "." {
|
|
| 86 |
- return nil, "", fmt.Errorf("cannot determine filename from url: %s", u)
|
|
| 87 |
- } |
|
| 88 |
- |
|
| 89 |
- resp, err := http.Get(src) |
|
| 90 |
- if err != nil {
|
|
| 91 |
- return nil, "", err |
|
| 92 |
- } |
|
| 93 |
- defer resp.Body.Close() |
|
| 94 |
- if resp.StatusCode >= 400 {
|
|
| 95 |
- return nil, "", fmt.Errorf("server returned a status code >= 400: %s", resp.Status)
|
|
| 96 |
- } |
|
| 97 |
- |
|
| 98 |
- tmpDir, err := ioutil.TempDir("", "dockerbuildurl-")
|
|
| 99 |
- if err != nil {
|
|
| 100 |
- return nil, "", err |
|
| 101 |
- } |
|
| 102 |
- tmpFileName := filepath.Join(tmpDir, base) |
|
| 103 |
- tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) |
|
| 104 |
- if err != nil {
|
|
| 105 |
- os.RemoveAll(tmpDir) |
|
| 106 |
- return nil, "", err |
|
| 107 |
- } |
|
| 108 |
- if _, err := io.Copy(tmpFile, resp.Body); err != nil {
|
|
| 109 |
- os.RemoveAll(tmpDir) |
|
| 110 |
- return nil, "", err |
|
| 111 |
- } |
|
| 112 |
- if err := tmpFile.Close(); err != nil {
|
|
| 113 |
- os.RemoveAll(tmpDir) |
|
| 114 |
- return nil, "", err |
|
| 115 |
- } |
|
| 116 |
- info, err := os.Stat(tmpFileName) |
|
| 117 |
- if err != nil {
|
|
| 118 |
- os.RemoveAll(tmpDir) |
|
| 119 |
- return nil, "", err |
|
| 120 |
- } |
|
| 121 |
- return []CopyInfo{{FileInfo: info, Path: base}}, tmpDir, nil
|
|
| 122 |
-} |
|
| 123 |
- |
|
| 124 |
-func trimLeadingPath(origPath string) string {
|
|
| 125 |
- // Work in daemon-specific OS filepath semantics |
|
| 126 |
- origPath = filepath.FromSlash(origPath) |
|
| 127 |
- if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 {
|
|
| 128 |
- origPath = origPath[1:] |
|
| 129 |
- } |
|
| 130 |
- origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator)) |
|
| 131 |
- return origPath |
|
| 132 |
-} |
|
| 133 |
- |
|
| 134 |
-func trimTrailingDot(origPath string) string {
|
|
| 135 |
- if strings.HasSuffix(origPath, string(os.PathSeparator)+".") {
|
|
| 136 |
- return strings.TrimSuffix(origPath, ".") |
|
| 137 |
- } |
|
| 138 |
- return origPath |
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-// containsWildcards checks whether the provided name has a wildcard. |
|
| 142 |
-func containsWildcards(name string) bool {
|
|
| 143 |
- for i := 0; i < len(name); i++ {
|
|
| 144 |
- ch := name[i] |
|
| 145 |
- if ch == '\\' {
|
|
| 146 |
- i++ |
|
| 147 |
- } else if ch == '*' || ch == '?' || ch == '[' {
|
|
| 148 |
- return true |
|
| 149 |
- } |
|
| 150 |
- } |
|
| 151 |
- return false |
|
| 152 |
-} |
| 153 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,441 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-// This file contains the dispatchers for each command. Note that |
|
| 4 |
-// `nullDispatch` is not actually a command, but support for commands we parse |
|
| 5 |
-// but do nothing with. |
|
| 6 |
-// |
|
| 7 |
-// See evaluator.go for a higher level discussion of the whole evaluator |
|
| 8 |
-// package. |
|
| 9 |
- |
|
| 10 |
-import ( |
|
| 11 |
- "fmt" |
|
| 12 |
- "os" |
|
| 13 |
- "path/filepath" |
|
| 14 |
- "regexp" |
|
| 15 |
- "runtime" |
|
| 16 |
- "strings" |
|
| 17 |
- |
|
| 18 |
- docker "github.com/fsouza/go-dockerclient" |
|
| 19 |
- |
|
| 20 |
- "github.com/openshift/origin/pkg/util/docker/dockerfile/builder/signal" |
|
| 21 |
- "github.com/openshift/origin/pkg/util/docker/dockerfile/builder/strslice" |
|
| 22 |
-) |
|
| 23 |
- |
|
| 24 |
-// dispatch with no layer / parsing. This is effectively not a command. |
|
| 25 |
-func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 26 |
- return nil |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-// ENV foo bar |
|
| 30 |
-// |
|
| 31 |
-// Sets the environment variable foo to bar, also makes interpolation |
|
| 32 |
-// in the dockerfile available from the next statement on via ${foo}.
|
|
| 33 |
-// |
|
| 34 |
-func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 35 |
- if len(args) == 0 {
|
|
| 36 |
- return errAtLeastOneArgument("ENV")
|
|
| 37 |
- } |
|
| 38 |
- |
|
| 39 |
- if len(args)%2 != 0 {
|
|
| 40 |
- // should never get here, but just in case |
|
| 41 |
- return errTooManyArguments("ENV")
|
|
| 42 |
- } |
|
| 43 |
- |
|
| 44 |
- // TODO/FIXME/NOT USED |
|
| 45 |
- // Just here to show how to use the builder flags stuff within the |
|
| 46 |
- // context of a builder command. Will remove once we actually add |
|
| 47 |
- // a builder command to something! |
|
| 48 |
- /* |
|
| 49 |
- flBool1 := b.flags.AddBool("bool1", false)
|
|
| 50 |
- flStr1 := b.flags.AddString("str1", "HI")
|
|
| 51 |
- |
|
| 52 |
- if err := b.flags.Parse(); err != nil {
|
|
| 53 |
- return err |
|
| 54 |
- } |
|
| 55 |
- |
|
| 56 |
- fmt.Printf("Bool1:%v\n", flBool1)
|
|
| 57 |
- fmt.Printf("Str1:%v\n", flStr1)
|
|
| 58 |
- */ |
|
| 59 |
- |
|
| 60 |
- for j := 0; j < len(args); j++ {
|
|
| 61 |
- // name ==> args[j] |
|
| 62 |
- // value ==> args[j+1] |
|
| 63 |
- newVar := args[j] + "=" + args[j+1] + "" |
|
| 64 |
- gotOne := false |
|
| 65 |
- for i, envVar := range b.RunConfig.Env {
|
|
| 66 |
- envParts := strings.SplitN(envVar, "=", 2) |
|
| 67 |
- if envParts[0] == args[j] {
|
|
| 68 |
- b.RunConfig.Env[i] = newVar |
|
| 69 |
- gotOne = true |
|
| 70 |
- break |
|
| 71 |
- } |
|
| 72 |
- } |
|
| 73 |
- if !gotOne {
|
|
| 74 |
- b.RunConfig.Env = append(b.RunConfig.Env, newVar) |
|
| 75 |
- } |
|
| 76 |
- j++ |
|
| 77 |
- } |
|
| 78 |
- |
|
| 79 |
- return nil |
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-// MAINTAINER some text <maybe@an.email.address> |
|
| 83 |
-// |
|
| 84 |
-// Sets the maintainer metadata. |
|
| 85 |
-func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 86 |
- if len(args) != 1 {
|
|
| 87 |
- return errExactlyOneArgument("MAINTAINER")
|
|
| 88 |
- } |
|
| 89 |
- b.Author = args[0] |
|
| 90 |
- return nil |
|
| 91 |
-} |
|
| 92 |
- |
|
| 93 |
-// LABEL some json data describing the image |
|
| 94 |
-// |
|
| 95 |
-// Sets the Label variable foo to bar, |
|
| 96 |
-// |
|
| 97 |
-func label(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 98 |
- if len(args) == 0 {
|
|
| 99 |
- return errAtLeastOneArgument("LABEL")
|
|
| 100 |
- } |
|
| 101 |
- if len(args)%2 != 0 {
|
|
| 102 |
- // should never get here, but just in case |
|
| 103 |
- return errTooManyArguments("LABEL")
|
|
| 104 |
- } |
|
| 105 |
- |
|
| 106 |
- if b.RunConfig.Labels == nil {
|
|
| 107 |
- b.RunConfig.Labels = map[string]string{}
|
|
| 108 |
- } |
|
| 109 |
- |
|
| 110 |
- for j := 0; j < len(args); j++ {
|
|
| 111 |
- // name ==> args[j] |
|
| 112 |
- // value ==> args[j+1] |
|
| 113 |
- b.RunConfig.Labels[args[j]] = args[j+1] |
|
| 114 |
- j++ |
|
| 115 |
- } |
|
| 116 |
- return nil |
|
| 117 |
-} |
|
| 118 |
- |
|
| 119 |
-// ADD foo /path |
|
| 120 |
-// |
|
| 121 |
-// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling |
|
| 122 |
-// exist here. If you do not wish to have this automatic handling, use COPY. |
|
| 123 |
-// |
|
| 124 |
-func add(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 125 |
- if len(args) < 2 {
|
|
| 126 |
- return errAtLeastOneArgument("ADD")
|
|
| 127 |
- } |
|
| 128 |
- for i := 1; i < len(args); i++ {
|
|
| 129 |
- args[i] = makeAbsolute(args[i], b.RunConfig.WorkingDir) |
|
| 130 |
- } |
|
| 131 |
- b.PendingCopies = append(b.PendingCopies, Copy{Src: args[0], Dest: args[1:], Download: true})
|
|
| 132 |
- return nil |
|
| 133 |
-} |
|
| 134 |
- |
|
| 135 |
-// COPY foo /path |
|
| 136 |
-// |
|
| 137 |
-// Same as 'ADD' but without the tar and remote url handling. |
|
| 138 |
-// |
|
| 139 |
-func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 140 |
- if len(args) < 2 {
|
|
| 141 |
- return errAtLeastOneArgument("COPY")
|
|
| 142 |
- } |
|
| 143 |
- for i := 1; i < len(args); i++ {
|
|
| 144 |
- args[i] = makeAbsolute(args[i], b.RunConfig.WorkingDir) |
|
| 145 |
- } |
|
| 146 |
- b.PendingCopies = append(b.PendingCopies, Copy{Src: args[0], Dest: args[1:], Download: false})
|
|
| 147 |
- return nil |
|
| 148 |
-} |
|
| 149 |
- |
|
| 150 |
-// FROM imagename |
|
| 151 |
-// |
|
| 152 |
-// This sets the image the dockerfile will build on top of. |
|
| 153 |
-// |
|
| 154 |
-func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 155 |
- if len(args) != 1 {
|
|
| 156 |
- return errExactlyOneArgument("FROM")
|
|
| 157 |
- } |
|
| 158 |
- |
|
| 159 |
- name := args[0] |
|
| 160 |
- // Windows cannot support a container with no base image. |
|
| 161 |
- if name == NoBaseImageSpecifier {
|
|
| 162 |
- if runtime.GOOS == "windows" {
|
|
| 163 |
- return fmt.Errorf("Windows does not support FROM scratch")
|
|
| 164 |
- } |
|
| 165 |
- } |
|
| 166 |
- b.RunConfig.Image = name |
|
| 167 |
- // TODO: handle onbuild |
|
| 168 |
- return nil |
|
| 169 |
-} |
|
| 170 |
- |
|
| 171 |
-// ONBUILD RUN echo yo |
|
| 172 |
-// |
|
| 173 |
-// ONBUILD triggers run when the image is used in a FROM statement. |
|
| 174 |
-// |
|
| 175 |
-// ONBUILD handling has a lot of special-case functionality, the heading in |
|
| 176 |
-// evaluator.go and comments around dispatch() in the same file explain the |
|
| 177 |
-// special cases. search for 'OnBuild' in internals.go for additional special |
|
| 178 |
-// cases. |
|
| 179 |
-// |
|
| 180 |
-func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 181 |
- if len(args) == 0 {
|
|
| 182 |
- return errAtLeastOneArgument("ONBUILD")
|
|
| 183 |
- } |
|
| 184 |
- |
|
| 185 |
- triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) |
|
| 186 |
- switch triggerInstruction {
|
|
| 187 |
- case "ONBUILD": |
|
| 188 |
- return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
|
| 189 |
- case "MAINTAINER", "FROM": |
|
| 190 |
- return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
|
| 191 |
- } |
|
| 192 |
- |
|
| 193 |
- original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") |
|
| 194 |
- |
|
| 195 |
- b.RunConfig.OnBuild = append(b.RunConfig.OnBuild, original) |
|
| 196 |
- return nil |
|
| 197 |
-} |
|
| 198 |
- |
|
| 199 |
-// WORKDIR /tmp |
|
| 200 |
-// |
|
| 201 |
-// Set the working directory for future RUN/CMD/etc statements. |
|
| 202 |
-// |
|
| 203 |
-func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 204 |
- if len(args) != 1 {
|
|
| 205 |
- return errExactlyOneArgument("WORKDIR")
|
|
| 206 |
- } |
|
| 207 |
- |
|
| 208 |
- // This is from the Dockerfile and will not necessarily be in platform |
|
| 209 |
- // specific semantics, hence ensure it is converted. |
|
| 210 |
- workdir := filepath.FromSlash(args[0]) |
|
| 211 |
- |
|
| 212 |
- if !filepath.IsAbs(workdir) {
|
|
| 213 |
- current := filepath.FromSlash(b.RunConfig.WorkingDir) |
|
| 214 |
- workdir = filepath.Join(string(os.PathSeparator), current, workdir) |
|
| 215 |
- } |
|
| 216 |
- |
|
| 217 |
- b.RunConfig.WorkingDir = workdir |
|
| 218 |
- return nil |
|
| 219 |
-} |
|
| 220 |
- |
|
| 221 |
-// RUN some command yo |
|
| 222 |
-// |
|
| 223 |
-// run a command and commit the image. Args are automatically prepended with |
|
| 224 |
-// 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is |
|
| 225 |
-// only one argument. The difference in processing: |
|
| 226 |
-// |
|
| 227 |
-// RUN echo hi # sh -c echo hi (Linux) |
|
| 228 |
-// RUN echo hi # cmd /S /C echo hi (Windows) |
|
| 229 |
-// RUN [ "echo", "hi" ] # echo hi |
|
| 230 |
-// |
|
| 231 |
-func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 232 |
- if b.RunConfig.Image == "" {
|
|
| 233 |
- return fmt.Errorf("Please provide a source image with `from` prior to run")
|
|
| 234 |
- } |
|
| 235 |
- |
|
| 236 |
- args = handleJSONArgs(args, attributes) |
|
| 237 |
- |
|
| 238 |
- run := Run{Args: args}
|
|
| 239 |
- |
|
| 240 |
- if !attributes["json"] {
|
|
| 241 |
- run.Shell = true |
|
| 242 |
- } |
|
| 243 |
- b.PendingRuns = append(b.PendingRuns, run) |
|
| 244 |
- return nil |
|
| 245 |
-} |
|
| 246 |
- |
|
| 247 |
-// CMD foo |
|
| 248 |
-// |
|
| 249 |
-// Set the default command to run in the container (which may be empty). |
|
| 250 |
-// Argument handling is the same as RUN. |
|
| 251 |
-// |
|
| 252 |
-func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 253 |
- cmdSlice := handleJSONArgs(args, attributes) |
|
| 254 |
- |
|
| 255 |
- if !attributes["json"] {
|
|
| 256 |
- if runtime.GOOS != "windows" {
|
|
| 257 |
- cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
|
|
| 258 |
- } else {
|
|
| 259 |
- cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...)
|
|
| 260 |
- } |
|
| 261 |
- } |
|
| 262 |
- |
|
| 263 |
- b.RunConfig.Cmd = strslice.StrSlice(cmdSlice) |
|
| 264 |
- if len(args) != 0 {
|
|
| 265 |
- b.CmdSet = true |
|
| 266 |
- } |
|
| 267 |
- return nil |
|
| 268 |
-} |
|
| 269 |
- |
|
| 270 |
-// ENTRYPOINT /usr/sbin/nginx |
|
| 271 |
-// |
|
| 272 |
-// Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to |
|
| 273 |
-// /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx. |
|
| 274 |
-// |
|
| 275 |
-// Handles command processing similar to CMD and RUN, only b.RunConfig.Entrypoint |
|
| 276 |
-// is initialized at NewBuilder time instead of through argument parsing. |
|
| 277 |
-// |
|
| 278 |
-func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 279 |
- parsed := handleJSONArgs(args, attributes) |
|
| 280 |
- |
|
| 281 |
- switch {
|
|
| 282 |
- case attributes["json"]: |
|
| 283 |
- // ENTRYPOINT ["echo", "hi"] |
|
| 284 |
- b.RunConfig.Entrypoint = strslice.StrSlice(parsed) |
|
| 285 |
- case len(parsed) == 0: |
|
| 286 |
- // ENTRYPOINT [] |
|
| 287 |
- b.RunConfig.Entrypoint = nil |
|
| 288 |
- default: |
|
| 289 |
- // ENTRYPOINT echo hi |
|
| 290 |
- if runtime.GOOS != "windows" {
|
|
| 291 |
- b.RunConfig.Entrypoint = strslice.StrSlice{"/bin/sh", "-c", parsed[0]}
|
|
| 292 |
- } else {
|
|
| 293 |
- b.RunConfig.Entrypoint = strslice.StrSlice{"cmd", "/S", "/C", parsed[0]}
|
|
| 294 |
- } |
|
| 295 |
- } |
|
| 296 |
- |
|
| 297 |
- // when setting the entrypoint if a CMD was not explicitly set then |
|
| 298 |
- // set the command to nil |
|
| 299 |
- if !b.CmdSet {
|
|
| 300 |
- b.RunConfig.Cmd = nil |
|
| 301 |
- } |
|
| 302 |
- return nil |
|
| 303 |
-} |
|
| 304 |
- |
|
| 305 |
-// EXPOSE 6667/tcp 7000/tcp |
|
| 306 |
-// |
|
| 307 |
-// Expose ports for links and port mappings. This all ends up in |
|
| 308 |
-// b.RunConfig.ExposedPorts for runconfig. |
|
| 309 |
-// |
|
| 310 |
-func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 311 |
- if len(args) == 0 {
|
|
| 312 |
- return errAtLeastOneArgument("EXPOSE")
|
|
| 313 |
- } |
|
| 314 |
- |
|
| 315 |
- if b.RunConfig.ExposedPorts == nil {
|
|
| 316 |
- b.RunConfig.ExposedPorts = make(map[docker.Port]struct{})
|
|
| 317 |
- } |
|
| 318 |
- |
|
| 319 |
- existing := map[string]struct{}{}
|
|
| 320 |
- for k := range b.RunConfig.ExposedPorts {
|
|
| 321 |
- existing[k.Port()] = struct{}{}
|
|
| 322 |
- } |
|
| 323 |
- |
|
| 324 |
- for _, port := range args {
|
|
| 325 |
- dp := docker.Port(port) |
|
| 326 |
- if _, exists := existing[dp.Port()]; !exists {
|
|
| 327 |
- b.RunConfig.ExposedPorts[docker.Port(fmt.Sprintf("%s/%s", dp.Port(), dp.Proto()))] = struct{}{}
|
|
| 328 |
- } |
|
| 329 |
- } |
|
| 330 |
- return nil |
|
| 331 |
-} |
|
| 332 |
- |
|
| 333 |
-// USER foo |
|
| 334 |
-// |
|
| 335 |
-// Set the user to 'foo' for future commands and when running the |
|
| 336 |
-// ENTRYPOINT/CMD at container run time. |
|
| 337 |
-// |
|
| 338 |
-func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 339 |
- if len(args) != 1 {
|
|
| 340 |
- return errExactlyOneArgument("USER")
|
|
| 341 |
- } |
|
| 342 |
- |
|
| 343 |
- b.RunConfig.User = args[0] |
|
| 344 |
- return nil |
|
| 345 |
-} |
|
| 346 |
- |
|
| 347 |
-// VOLUME /foo |
|
| 348 |
-// |
|
| 349 |
-// Expose the volume /foo for use. Will also accept the JSON array form. |
|
| 350 |
-// |
|
| 351 |
-func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 352 |
- if len(args) == 0 {
|
|
| 353 |
- return errAtLeastOneArgument("VOLUME")
|
|
| 354 |
- } |
|
| 355 |
- |
|
| 356 |
- if b.RunConfig.Volumes == nil {
|
|
| 357 |
- b.RunConfig.Volumes = map[string]struct{}{}
|
|
| 358 |
- } |
|
| 359 |
- for _, v := range args {
|
|
| 360 |
- v = strings.TrimSpace(v) |
|
| 361 |
- if v == "" {
|
|
| 362 |
- return fmt.Errorf("Volume specified can not be an empty string")
|
|
| 363 |
- } |
|
| 364 |
- b.RunConfig.Volumes[v] = struct{}{}
|
|
| 365 |
- } |
|
| 366 |
- return nil |
|
| 367 |
-} |
|
| 368 |
- |
|
| 369 |
-// STOPSIGNAL signal |
|
| 370 |
-// |
|
| 371 |
-// Set the signal that will be used to kill the container. |
|
| 372 |
-func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 373 |
- if len(args) != 1 {
|
|
| 374 |
- return fmt.Errorf("STOPSIGNAL requires exactly one argument")
|
|
| 375 |
- } |
|
| 376 |
- |
|
| 377 |
- sig := args[0] |
|
| 378 |
- _, err := signal.ParseSignal(sig) |
|
| 379 |
- if err != nil {
|
|
| 380 |
- return err |
|
| 381 |
- } |
|
| 382 |
- |
|
| 383 |
- b.RunConfig.StopSignal = sig |
|
| 384 |
- return nil |
|
| 385 |
-} |
|
| 386 |
- |
|
| 387 |
-// ARG name[=value] |
|
| 388 |
-// |
|
| 389 |
-// Adds the variable foo to the trusted list of variables that can be passed |
|
| 390 |
-// to builder using the --build-arg flag for expansion/subsitution or passing to 'run'. |
|
| 391 |
-// Dockerfile author may optionally set a default value of this variable. |
|
| 392 |
-func arg(b *Builder, args []string, attributes map[string]bool, original string) error {
|
|
| 393 |
- if len(args) != 1 {
|
|
| 394 |
- return fmt.Errorf("ARG requires exactly one argument definition")
|
|
| 395 |
- } |
|
| 396 |
- |
|
| 397 |
- var ( |
|
| 398 |
- name string |
|
| 399 |
- value string |
|
| 400 |
- hasDefault bool |
|
| 401 |
- ) |
|
| 402 |
- |
|
| 403 |
- arg := args[0] |
|
| 404 |
- // 'arg' can just be a name or name-value pair. Note that this is different |
|
| 405 |
- // from 'env' that handles the split of name and value at the parser level. |
|
| 406 |
- // The reason for doing it differently for 'arg' is that we support just |
|
| 407 |
- // defining an arg and not assign it a value (while 'env' always expects a |
|
| 408 |
- // name-value pair). If possible, it will be good to harmonize the two. |
|
| 409 |
- if strings.Contains(arg, "=") {
|
|
| 410 |
- parts := strings.SplitN(arg, "=", 2) |
|
| 411 |
- name = parts[0] |
|
| 412 |
- value = parts[1] |
|
| 413 |
- hasDefault = true |
|
| 414 |
- } else {
|
|
| 415 |
- name = arg |
|
| 416 |
- hasDefault = false |
|
| 417 |
- } |
|
| 418 |
- // add the arg to allowed list of build-time args from this step on. |
|
| 419 |
- b.AllowedArgs[name] = true |
|
| 420 |
- |
|
| 421 |
- // If there is a default value associated with this arg then add it to the |
|
| 422 |
- // b.buildArgs if one is not already passed to the builder. The args passed |
|
| 423 |
- // to builder override the default value of 'arg'. |
|
| 424 |
- if _, ok := b.Args[name]; !ok && hasDefault {
|
|
| 425 |
- b.Args[name] = value |
|
| 426 |
- } |
|
| 427 |
- |
|
| 428 |
- return nil |
|
| 429 |
-} |
|
| 430 |
- |
|
| 431 |
-func errAtLeastOneArgument(command string) error {
|
|
| 432 |
- return fmt.Errorf("%s requires at least one argument", command)
|
|
| 433 |
-} |
|
| 434 |
- |
|
| 435 |
-func errExactlyOneArgument(command string) error {
|
|
| 436 |
- return fmt.Errorf("%s requires exactly one argument", command)
|
|
| 437 |
-} |
|
| 438 |
- |
|
| 439 |
-func errTooManyArguments(command string) error {
|
|
| 440 |
- return fmt.Errorf("Bad input to %s, too many arguments", command)
|
|
| 441 |
-} |
| 442 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,6 +0,0 @@ |
| 1 |
-// Package builder uses code from github.com/docker/docker/builder/* to implement |
|
| 2 |
-// a Docker builder that does not create individual layers, but instead creates a |
|
| 3 |
-// single layer. |
|
| 4 |
-// |
|
| 5 |
-// TODO: full windows support |
|
| 6 |
-package builder |
| 7 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,150 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "strings" |
|
| 6 |
- |
|
| 7 |
- "github.com/docker/docker/builder/command" |
|
| 8 |
- "github.com/docker/docker/builder/parser" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// Environment variable interpolation will happen on these statements only. |
|
| 12 |
-var replaceEnvAllowed = map[string]bool{
|
|
| 13 |
- command.Env: true, |
|
| 14 |
- command.Label: true, |
|
| 15 |
- command.Add: true, |
|
| 16 |
- command.Copy: true, |
|
| 17 |
- command.Workdir: true, |
|
| 18 |
- command.Expose: true, |
|
| 19 |
- command.Volume: true, |
|
| 20 |
- command.User: true, |
|
| 21 |
- commandStopSignal: true, |
|
| 22 |
- commandArg: true, |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-// Certain commands are allowed to have their args split into more |
|
| 26 |
-// words after env var replacements. Meaning: |
|
| 27 |
-// ENV foo="123 456" |
|
| 28 |
-// EXPOSE $foo |
|
| 29 |
-// should result in the same thing as: |
|
| 30 |
-// EXPOSE 123 456 |
|
| 31 |
-// and not treat "123 456" as a single word. |
|
| 32 |
-// Note that: EXPOSE "$foo" and EXPOSE $foo are not the same thing. |
|
| 33 |
-// Quotes will cause it to still be treated as single word. |
|
| 34 |
-var allowWordExpansion = map[string]bool{
|
|
| 35 |
- command.Expose: true, |
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-// Step represents the input Env and the output command after all |
|
| 39 |
-// post processing of the command arguments is done. |
|
| 40 |
-type Step struct {
|
|
| 41 |
- Env []string |
|
| 42 |
- |
|
| 43 |
- Command string |
|
| 44 |
- Args []string |
|
| 45 |
- Flags []string |
|
| 46 |
- Attrs map[string]bool |
|
| 47 |
- Message string |
|
| 48 |
- Original string |
|
| 49 |
-} |
|
| 50 |
- |
|
| 51 |
-// Resolve transforms a parsed Dockerfile line into a command to execute, |
|
| 52 |
-// resolving any arguments. |
|
| 53 |
-// |
|
| 54 |
-// Almost all nodes will have this structure: |
|
| 55 |
-// Child[Node, Node, Node] where Child is from parser.Node.Children and each |
|
| 56 |
-// node comes from parser.Node.Next. This forms a "line" with a statement and |
|
| 57 |
-// arguments and we process them in this normalized form by hitting |
|
| 58 |
-// evaluateTable with the leaf nodes of the command and the Builder object. |
|
| 59 |
-// |
|
| 60 |
-// ONBUILD is a special case; in this case the parser will emit: |
|
| 61 |
-// Child[Node, Child[Node, Node...]] where the first node is the literal |
|
| 62 |
-// "onbuild" and the child entrypoint is the command of the ONBUILD statement, |
|
| 63 |
-// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to |
|
| 64 |
-// deal with that, at least until it becomes more of a general concern with new |
|
| 65 |
-// features. |
|
| 66 |
-func (b *Step) Resolve(ast *parser.Node) error {
|
|
| 67 |
- cmd := ast.Value |
|
| 68 |
- upperCasedCmd := strings.ToUpper(cmd) |
|
| 69 |
- |
|
| 70 |
- // To ensure the user is given a decent error message if the platform |
|
| 71 |
- // on which the daemon is running does not support a builder command. |
|
| 72 |
- if err := platformSupports(strings.ToLower(cmd)); err != nil {
|
|
| 73 |
- return err |
|
| 74 |
- } |
|
| 75 |
- |
|
| 76 |
- attrs := ast.Attributes |
|
| 77 |
- original := ast.Original |
|
| 78 |
- flags := ast.Flags |
|
| 79 |
- strList := []string{}
|
|
| 80 |
- msg := upperCasedCmd |
|
| 81 |
- |
|
| 82 |
- if len(ast.Flags) > 0 {
|
|
| 83 |
- msg += " " + strings.Join(ast.Flags, " ") |
|
| 84 |
- } |
|
| 85 |
- |
|
| 86 |
- if cmd == "onbuild" {
|
|
| 87 |
- if ast.Next == nil {
|
|
| 88 |
- return fmt.Errorf("ONBUILD requires at least one argument")
|
|
| 89 |
- } |
|
| 90 |
- ast = ast.Next.Children[0] |
|
| 91 |
- strList = append(strList, ast.Value) |
|
| 92 |
- msg += " " + ast.Value |
|
| 93 |
- |
|
| 94 |
- if len(ast.Flags) > 0 {
|
|
| 95 |
- msg += " " + strings.Join(ast.Flags, " ") |
|
| 96 |
- } |
|
| 97 |
- |
|
| 98 |
- } |
|
| 99 |
- |
|
| 100 |
- // count the number of nodes that we are going to traverse first |
|
| 101 |
- // so we can pre-create the argument and message array. This speeds up the |
|
| 102 |
- // allocation of those list a lot when they have a lot of arguments |
|
| 103 |
- cursor := ast |
|
| 104 |
- var n int |
|
| 105 |
- for cursor.Next != nil {
|
|
| 106 |
- cursor = cursor.Next |
|
| 107 |
- n++ |
|
| 108 |
- } |
|
| 109 |
- msgList := make([]string, n) |
|
| 110 |
- |
|
| 111 |
- var i int |
|
| 112 |
- envs := b.Env |
|
| 113 |
- for ast.Next != nil {
|
|
| 114 |
- ast = ast.Next |
|
| 115 |
- var str string |
|
| 116 |
- str = ast.Value |
|
| 117 |
- if replaceEnvAllowed[cmd] {
|
|
| 118 |
- var err error |
|
| 119 |
- var words []string |
|
| 120 |
- |
|
| 121 |
- if allowWordExpansion[cmd] {
|
|
| 122 |
- words, err = ProcessWords(str, envs) |
|
| 123 |
- if err != nil {
|
|
| 124 |
- return err |
|
| 125 |
- } |
|
| 126 |
- strList = append(strList, words...) |
|
| 127 |
- } else {
|
|
| 128 |
- str, err = ProcessWord(str, envs) |
|
| 129 |
- if err != nil {
|
|
| 130 |
- return err |
|
| 131 |
- } |
|
| 132 |
- strList = append(strList, str) |
|
| 133 |
- } |
|
| 134 |
- } else {
|
|
| 135 |
- strList = append(strList, str) |
|
| 136 |
- } |
|
| 137 |
- msgList[i] = ast.Value |
|
| 138 |
- i++ |
|
| 139 |
- } |
|
| 140 |
- |
|
| 141 |
- msg += " " + strings.Join(msgList, " ") |
|
| 142 |
- |
|
| 143 |
- b.Message = msg |
|
| 144 |
- b.Command = cmd |
|
| 145 |
- b.Args = strList |
|
| 146 |
- b.Original = original |
|
| 147 |
- b.Attrs = attrs |
|
| 148 |
- b.Flags = flags |
|
| 149 |
- return nil |
|
| 150 |
-} |
| 151 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,292 +0,0 @@ |
| 1 |
-package imageprogress |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bytes" |
|
| 5 |
- "encoding/json" |
|
| 6 |
- "errors" |
|
| 7 |
- "fmt" |
|
| 8 |
- "io" |
|
| 9 |
- "regexp" |
|
| 10 |
- "strings" |
|
| 11 |
- "sync" |
|
| 12 |
- "time" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-const ( |
|
| 16 |
- defaultProgressTimeThreshhold = 30 * time.Second |
|
| 17 |
- defaultStableThreshhold = 10 |
|
| 18 |
-) |
|
| 19 |
- |
|
| 20 |
-// progressLine is a structure representation of a Docker pull progress line |
|
| 21 |
-type progressLine struct {
|
|
| 22 |
- ID string `json:"id"` |
|
| 23 |
- Status string `json:"status"` |
|
| 24 |
- Detail *progressDetail `json:"progressDetail"` |
|
| 25 |
- Error string `json:"error"` |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-// progressDetail is the progressDetail structure in a Docker pull progress line |
|
| 29 |
-type progressDetail struct {
|
|
| 30 |
- Current int64 `json:"current"` |
|
| 31 |
- Total int64 `json:"total"` |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 |
-// layerDetail is layer information associated with a specific layerStatus |
|
| 35 |
-type layerDetail struct {
|
|
| 36 |
- Count int |
|
| 37 |
- Current int64 |
|
| 38 |
- Total int64 |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-// layerStatus is one of different possible status for layers detected by |
|
| 42 |
-// the ProgressWriter |
|
| 43 |
-type layerStatus int |
|
| 44 |
- |
|
| 45 |
-const ( |
|
| 46 |
- statusPending layerStatus = iota |
|
| 47 |
- statusDownloading |
|
| 48 |
- statusExtracting |
|
| 49 |
- statusComplete |
|
| 50 |
- statusPushing |
|
| 51 |
-) |
|
| 52 |
- |
|
| 53 |
-// layerStatusFromDockerString translates a string in a Docker status |
|
| 54 |
-// line to a layerStatus |
|
| 55 |
-func layerStatusFromDockerString(dockerStatus string) layerStatus {
|
|
| 56 |
- switch dockerStatus {
|
|
| 57 |
- case "Pushing": |
|
| 58 |
- return statusPushing |
|
| 59 |
- case "Downloading": |
|
| 60 |
- return statusDownloading |
|
| 61 |
- case "Extracting", "Verifying Checksum", "Download complete": |
|
| 62 |
- return statusExtracting |
|
| 63 |
- case "Pull complete", "Already exists", "Pushed", "Layer already exists": |
|
| 64 |
- return statusComplete |
|
| 65 |
- default: |
|
| 66 |
- return statusPending |
|
| 67 |
- } |
|
| 68 |
-} |
|
| 69 |
- |
|
| 70 |
-type report map[layerStatus]*layerDetail |
|
| 71 |
- |
|
| 72 |
-func (r report) count(status layerStatus) int {
|
|
| 73 |
- detail, ok := r[status] |
|
| 74 |
- if !ok {
|
|
| 75 |
- return 0 |
|
| 76 |
- } |
|
| 77 |
- return detail.Count |
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 |
-func (r report) percentProgress(status layerStatus) float32 {
|
|
| 81 |
- detail, ok := r[status] |
|
| 82 |
- if !ok {
|
|
| 83 |
- return 0 |
|
| 84 |
- } |
|
| 85 |
- if detail.Total == 0 {
|
|
| 86 |
- return 0 |
|
| 87 |
- } |
|
| 88 |
- pct := float32(detail.Current) / float32(detail.Total) * 100.0 |
|
| 89 |
- if pct > 100.0 {
|
|
| 90 |
- pct = 100.0 |
|
| 91 |
- } |
|
| 92 |
- return pct |
|
| 93 |
-} |
|
| 94 |
- |
|
| 95 |
-func (r report) totalCount() int {
|
|
| 96 |
- cnt := 0 |
|
| 97 |
- for _, detail := range r {
|
|
| 98 |
- cnt += detail.Count |
|
| 99 |
- } |
|
| 100 |
- return cnt |
|
| 101 |
-} |
|
| 102 |
- |
|
| 103 |
-// String is used for test output |
|
| 104 |
-func (r report) String() string {
|
|
| 105 |
- result := &bytes.Buffer{}
|
|
| 106 |
- fmt.Fprintf(result, "{")
|
|
| 107 |
- for k := range r {
|
|
| 108 |
- var status string |
|
| 109 |
- switch k {
|
|
| 110 |
- case statusPending: |
|
| 111 |
- status = "pending" |
|
| 112 |
- case statusDownloading: |
|
| 113 |
- status = "downloading" |
|
| 114 |
- case statusExtracting: |
|
| 115 |
- status = "extracting" |
|
| 116 |
- case statusComplete: |
|
| 117 |
- status = "complete" |
|
| 118 |
- } |
|
| 119 |
- fmt.Fprintf(result, "%s:{Count: %d, Current: %d, Total: %d}, ", status, r[k].Count, r[k].Current, r[k].Total)
|
|
| 120 |
- } |
|
| 121 |
- fmt.Fprintf(result, "}") |
|
| 122 |
- return result.String() |
|
| 123 |
-} |
|
| 124 |
- |
|
| 125 |
-// newWriter creates a writer that periodically reports |
|
| 126 |
-// on pull/push progress of a Docker image. It only reports when the state of the |
|
| 127 |
-// different layers has changed and uses time thresholds to limit the |
|
| 128 |
-// rate of the reports. |
|
| 129 |
-func newWriter(reportFn func(report), layersChangedFn func(report, report) bool) io.Writer {
|
|
| 130 |
- writer := &imageProgressWriter{
|
|
| 131 |
- mutex: &sync.Mutex{},
|
|
| 132 |
- layerStatus: map[string]progressLine{},
|
|
| 133 |
- reportFn: reportFn, |
|
| 134 |
- layersChangedFn: layersChangedFn, |
|
| 135 |
- progressTimeThreshhold: defaultProgressTimeThreshhold, |
|
| 136 |
- stableThreshhold: defaultStableThreshhold, |
|
| 137 |
- } |
|
| 138 |
- return writer |
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-type imageProgressWriter struct {
|
|
| 142 |
- mutex *sync.Mutex |
|
| 143 |
- internalWriter io.Writer |
|
| 144 |
- layerStatus map[string]progressLine |
|
| 145 |
- lastLayerCount int |
|
| 146 |
- stableLines int |
|
| 147 |
- stableThreshhold int |
|
| 148 |
- progressTimeThreshhold time.Duration |
|
| 149 |
- lastReport report |
|
| 150 |
- lastReportTime time.Time |
|
| 151 |
- reportFn func(report) |
|
| 152 |
- layersChangedFn func(report, report) bool |
|
| 153 |
-} |
|
| 154 |
- |
|
| 155 |
-func (w *imageProgressWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
| 156 |
- decoder := json.NewDecoder(reader) |
|
| 157 |
- return 0, w.readProgress(decoder) |
|
| 158 |
-} |
|
| 159 |
- |
|
| 160 |
-func (w *imageProgressWriter) Write(data []byte) (int, error) {
|
|
| 161 |
- w.mutex.Lock() |
|
| 162 |
- defer w.mutex.Unlock() |
|
| 163 |
- if w.internalWriter == nil {
|
|
| 164 |
- var pipeIn *io.PipeReader |
|
| 165 |
- pipeIn, w.internalWriter = io.Pipe() |
|
| 166 |
- decoder := json.NewDecoder(pipeIn) |
|
| 167 |
- go func() {
|
|
| 168 |
- err := w.readProgress(decoder) |
|
| 169 |
- if err != nil {
|
|
| 170 |
- pipeIn.CloseWithError(err) |
|
| 171 |
- } |
|
| 172 |
- }() |
|
| 173 |
- } |
|
| 174 |
- return w.internalWriter.Write(data) |
|
| 175 |
-} |
|
| 176 |
- |
|
| 177 |
-func (w *imageProgressWriter) readProgress(decoder *json.Decoder) error {
|
|
| 178 |
- for {
|
|
| 179 |
- line := &progressLine{}
|
|
| 180 |
- err := decoder.Decode(line) |
|
| 181 |
- if err == io.EOF {
|
|
| 182 |
- break |
|
| 183 |
- } |
|
| 184 |
- if err != nil {
|
|
| 185 |
- return err |
|
| 186 |
- } |
|
| 187 |
- err = w.processLine(line) |
|
| 188 |
- if err != nil {
|
|
| 189 |
- return err |
|
| 190 |
- } |
|
| 191 |
- } |
|
| 192 |
- return nil |
|
| 193 |
-} |
|
| 194 |
- |
|
| 195 |
-func (w *imageProgressWriter) processLine(line *progressLine) error {
|
|
| 196 |
- |
|
| 197 |
- if err := getError(line); err != nil {
|
|
| 198 |
- return err |
|
| 199 |
- } |
|
| 200 |
- |
|
| 201 |
- // determine if it's a line we want to process |
|
| 202 |
- if !islayerStatus(line) {
|
|
| 203 |
- return nil |
|
| 204 |
- } |
|
| 205 |
- |
|
| 206 |
- w.layerStatus[line.ID] = *line |
|
| 207 |
- |
|
| 208 |
- // if the number of layers has not stabilized yet, return and wait for more |
|
| 209 |
- // progress |
|
| 210 |
- if !w.isStableLayerCount() {
|
|
| 211 |
- return nil |
|
| 212 |
- } |
|
| 213 |
- |
|
| 214 |
- r := createReport(w.layerStatus) |
|
| 215 |
- |
|
| 216 |
- // check if the count of layers in each state has changed |
|
| 217 |
- if w.layersChangedFn(w.lastReport, r) {
|
|
| 218 |
- w.lastReport = r |
|
| 219 |
- w.lastReportTime = time.Now() |
|
| 220 |
- w.reportFn(r) |
|
| 221 |
- return nil |
|
| 222 |
- } |
|
| 223 |
- // If layer counts haven't changed, but enough time has passed (30 sec by default), |
|
| 224 |
- // at least report on download/push progress |
|
| 225 |
- if time.Since(w.lastReportTime) > w.progressTimeThreshhold {
|
|
| 226 |
- w.lastReport = r |
|
| 227 |
- w.lastReportTime = time.Now() |
|
| 228 |
- w.reportFn(r) |
|
| 229 |
- } |
|
| 230 |
- return nil |
|
| 231 |
-} |
|
| 232 |
- |
|
| 233 |
-func (w *imageProgressWriter) isStableLayerCount() bool {
|
|
| 234 |
- // If the number of layers has changed since last status, we're not stable |
|
| 235 |
- if w.lastLayerCount != len(w.layerStatus) {
|
|
| 236 |
- w.lastLayerCount = len(w.layerStatus) |
|
| 237 |
- w.stableLines = 0 |
|
| 238 |
- return false |
|
| 239 |
- } |
|
| 240 |
- // Only proceed after we've received status for the same number |
|
| 241 |
- // of layers at least stableThreshhold times. If not, they're still increasing |
|
| 242 |
- w.stableLines++ |
|
| 243 |
- if w.stableLines < w.stableThreshhold {
|
|
| 244 |
- // We're not stable enough yet |
|
| 245 |
- return false |
|
| 246 |
- } |
|
| 247 |
- |
|
| 248 |
- return true |
|
| 249 |
-} |
|
| 250 |
- |
|
| 251 |
-var layerIDRegexp = regexp.MustCompile("^[a-f,0-9]*$")
|
|
| 252 |
- |
|
| 253 |
-func islayerStatus(line *progressLine) bool {
|
|
| 254 |
- // ignore status lines with no layer id |
|
| 255 |
- if len(line.ID) == 0 {
|
|
| 256 |
- return false |
|
| 257 |
- } |
|
| 258 |
- // ignore layer ids that are not hex string |
|
| 259 |
- if !layerIDRegexp.MatchString(line.ID) {
|
|
| 260 |
- return false |
|
| 261 |
- } |
|
| 262 |
- // ignore retrying status |
|
| 263 |
- if strings.HasPrefix(line.Status, "Retrying") {
|
|
| 264 |
- return false |
|
| 265 |
- } |
|
| 266 |
- return true |
|
| 267 |
-} |
|
| 268 |
- |
|
| 269 |
-func getError(line *progressLine) error {
|
|
| 270 |
- if len(line.Error) > 0 {
|
|
| 271 |
- return errors.New(line.Error) |
|
| 272 |
- } |
|
| 273 |
- return nil |
|
| 274 |
-} |
|
| 275 |
- |
|
| 276 |
-func createReport(dockerProgress map[string]progressLine) report {
|
|
| 277 |
- r := report{}
|
|
| 278 |
- for _, line := range dockerProgress {
|
|
| 279 |
- layerStatus := layerStatusFromDockerString(line.Status) |
|
| 280 |
- detail, exists := r[layerStatus] |
|
| 281 |
- if !exists {
|
|
| 282 |
- detail = &layerDetail{}
|
|
| 283 |
- r[layerStatus] = detail |
|
| 284 |
- } |
|
| 285 |
- detail.Count++ |
|
| 286 |
- if line.Detail != nil {
|
|
| 287 |
- detail.Current += line.Detail.Current |
|
| 288 |
- detail.Total += line.Detail.Total |
|
| 289 |
- } |
|
| 290 |
- } |
|
| 291 |
- return r |
|
| 292 |
-} |
| 293 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,208 +0,0 @@ |
| 1 |
-package imageprogress |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/json" |
|
| 5 |
- "io" |
|
| 6 |
- "reflect" |
|
| 7 |
- "strconv" |
|
| 8 |
- "testing" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-func TestReports(t *testing.T) {
|
|
| 12 |
- tests := []struct {
|
|
| 13 |
- name string |
|
| 14 |
- gen func(*progressGenerator) |
|
| 15 |
- errExpected bool |
|
| 16 |
- expected report |
|
| 17 |
- }{
|
|
| 18 |
- {
|
|
| 19 |
- name: "simple report", |
|
| 20 |
- gen: func(p *progressGenerator) {
|
|
| 21 |
- p.status("1", "Extracting")
|
|
| 22 |
- p.status("2", "Downloading")
|
|
| 23 |
- p.status("1", "Downloading")
|
|
| 24 |
- p.status("2", "Pull complete")
|
|
| 25 |
- }, |
|
| 26 |
- expected: report{
|
|
| 27 |
- statusDownloading: &layerDetail{Count: 1},
|
|
| 28 |
- statusComplete: &layerDetail{Count: 1},
|
|
| 29 |
- }, |
|
| 30 |
- }, |
|
| 31 |
- {
|
|
| 32 |
- name: "ignore invalid layer id", |
|
| 33 |
- gen: func(p *progressGenerator) {
|
|
| 34 |
- p.status("1", "Downloading")
|
|
| 35 |
- p.status("hello", "testing")
|
|
| 36 |
- p.status("1", "Downloading")
|
|
| 37 |
- }, |
|
| 38 |
- expected: report{
|
|
| 39 |
- statusDownloading: &layerDetail{Count: 1},
|
|
| 40 |
- }, |
|
| 41 |
- }, |
|
| 42 |
- {
|
|
| 43 |
- name: "ignore retrying status", |
|
| 44 |
- gen: func(p *progressGenerator) {
|
|
| 45 |
- p.status("1", "Downloading")
|
|
| 46 |
- p.status("2", "Pull complete")
|
|
| 47 |
- p.status("1", "Downloading")
|
|
| 48 |
- p.status("3", "Retrying")
|
|
| 49 |
- }, |
|
| 50 |
- expected: report{
|
|
| 51 |
- statusDownloading: &layerDetail{Count: 1},
|
|
| 52 |
- statusComplete: &layerDetail{Count: 1},
|
|
| 53 |
- }, |
|
| 54 |
- }, |
|
| 55 |
- {
|
|
| 56 |
- name: "detect error", |
|
| 57 |
- gen: func(p *progressGenerator) {
|
|
| 58 |
- p.status("1", "Downloading")
|
|
| 59 |
- p.err("an error")
|
|
| 60 |
- }, |
|
| 61 |
- errExpected: true, |
|
| 62 |
- }, |
|
| 63 |
- } |
|
| 64 |
- |
|
| 65 |
- for _, test := range tests {
|
|
| 66 |
- pipeIn, pipeOut := io.Pipe() |
|
| 67 |
- go func() {
|
|
| 68 |
- p := newProgressGenerator(pipeOut) |
|
| 69 |
- test.gen(p) |
|
| 70 |
- pipeOut.Close() |
|
| 71 |
- }() |
|
| 72 |
- var lastReport report |
|
| 73 |
- w := newWriter( |
|
| 74 |
- func(r report) {
|
|
| 75 |
- lastReport = r |
|
| 76 |
- }, |
|
| 77 |
- func(a report, b report) bool {
|
|
| 78 |
- return true |
|
| 79 |
- }, |
|
| 80 |
- ) |
|
| 81 |
- w.(*imageProgressWriter).stableThreshhold = 0 |
|
| 82 |
- _, err := io.Copy(w, pipeIn) |
|
| 83 |
- if err != nil {
|
|
| 84 |
- if !test.errExpected {
|
|
| 85 |
- t.Errorf("%s: unexpected: %v", test.name, err)
|
|
| 86 |
- } |
|
| 87 |
- continue |
|
| 88 |
- } |
|
| 89 |
- if test.errExpected {
|
|
| 90 |
- t.Errorf("%s: did not get expected error", test.name)
|
|
| 91 |
- continue |
|
| 92 |
- } |
|
| 93 |
- if !compareReport(lastReport, test.expected) {
|
|
| 94 |
- t.Errorf("%s: unexpected report, got: %v, expected: %v", test.name, lastReport, test.expected)
|
|
| 95 |
- } |
|
| 96 |
- } |
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 |
-func TestErrorOnCopy(t *testing.T) {
|
|
| 100 |
- // Producer pipe |
|
| 101 |
- genIn, genOut := io.Pipe() |
|
| 102 |
- p := newProgressGenerator(genOut) |
|
| 103 |
- |
|
| 104 |
- // generate some data |
|
| 105 |
- go func() {
|
|
| 106 |
- for i := 0; i < 100; i++ {
|
|
| 107 |
- p.status("1", "Downloading")
|
|
| 108 |
- p.status("2", "Downloading")
|
|
| 109 |
- p.status("3", "Downloading")
|
|
| 110 |
- } |
|
| 111 |
- p.err("data error")
|
|
| 112 |
- genOut.Close() |
|
| 113 |
- }() |
|
| 114 |
- |
|
| 115 |
- w := newWriter(func(r report) {}, func(a, b report) bool { return true })
|
|
| 116 |
- |
|
| 117 |
- // Ensure that the error is propagated to the copy |
|
| 118 |
- _, err := io.Copy(w, genIn) |
|
| 119 |
- if err == nil {
|
|
| 120 |
- t.Errorf("Did not get an error when copying to writer")
|
|
| 121 |
- } |
|
| 122 |
- if err.Error() != "data error" {
|
|
| 123 |
- t.Errorf("Did not get expected error: %v", err)
|
|
| 124 |
- } |
|
| 125 |
-} |
|
| 126 |
- |
|
| 127 |
-func TestStableLayerCount(t *testing.T) {
|
|
| 128 |
- |
|
| 129 |
- tests := []struct {
|
|
| 130 |
- name string |
|
| 131 |
- lastLayerCount int |
|
| 132 |
- layerStatusCount int |
|
| 133 |
- stableThreshhold int |
|
| 134 |
- callCount int |
|
| 135 |
- expectStable bool |
|
| 136 |
- }{
|
|
| 137 |
- {
|
|
| 138 |
- name: "increasing layer count", |
|
| 139 |
- lastLayerCount: 3, |
|
| 140 |
- layerStatusCount: 4, |
|
| 141 |
- callCount: 1, |
|
| 142 |
- expectStable: false, |
|
| 143 |
- }, |
|
| 144 |
- {
|
|
| 145 |
- name: "has not met stable threshhold", |
|
| 146 |
- lastLayerCount: 3, |
|
| 147 |
- layerStatusCount: 3, |
|
| 148 |
- callCount: 2, |
|
| 149 |
- stableThreshhold: 3, |
|
| 150 |
- expectStable: false, |
|
| 151 |
- }, |
|
| 152 |
- {
|
|
| 153 |
- name: "met stable threshhold", |
|
| 154 |
- lastLayerCount: 3, |
|
| 155 |
- layerStatusCount: 3, |
|
| 156 |
- callCount: 4, |
|
| 157 |
- stableThreshhold: 3, |
|
| 158 |
- expectStable: true, |
|
| 159 |
- }, |
|
| 160 |
- } |
|
| 161 |
- for _, test := range tests {
|
|
| 162 |
- w := newWriter(func(report) {}, func(a, b report) bool { return true }).(*imageProgressWriter)
|
|
| 163 |
- w.lastLayerCount = test.lastLayerCount |
|
| 164 |
- w.layerStatus = map[string]progressLine{}
|
|
| 165 |
- w.stableThreshhold = test.stableThreshhold |
|
| 166 |
- for i := 0; i < test.layerStatusCount; i++ {
|
|
| 167 |
- w.layerStatus[strconv.Itoa(i)] = progressLine{}
|
|
| 168 |
- } |
|
| 169 |
- var result bool |
|
| 170 |
- for i := 0; i < test.callCount; i++ {
|
|
| 171 |
- result = w.isStableLayerCount() |
|
| 172 |
- } |
|
| 173 |
- if result != test.expectStable {
|
|
| 174 |
- t.Errorf("%s: expected %v, got %v", test.name, test.expectStable, result)
|
|
| 175 |
- } |
|
| 176 |
- } |
|
| 177 |
-} |
|
| 178 |
- |
|
| 179 |
-func compareReport(a, b report) bool {
|
|
| 180 |
- if len(a) != len(b) {
|
|
| 181 |
- return false |
|
| 182 |
- } |
|
| 183 |
- for k := range a {
|
|
| 184 |
- if _, ok := b[k]; !ok {
|
|
| 185 |
- return false |
|
| 186 |
- } |
|
| 187 |
- if !reflect.DeepEqual(*a[k], *b[k]) {
|
|
| 188 |
- return false |
|
| 189 |
- } |
|
| 190 |
- } |
|
| 191 |
- return true |
|
| 192 |
-} |
|
| 193 |
- |
|
| 194 |
-type progressGenerator json.Encoder |
|
| 195 |
- |
|
| 196 |
-func newProgressGenerator(w io.Writer) *progressGenerator {
|
|
| 197 |
- return (*progressGenerator)(json.NewEncoder(w)) |
|
| 198 |
-} |
|
| 199 |
- |
|
| 200 |
-func (p *progressGenerator) status(id, status string) {
|
|
| 201 |
- (*json.Encoder)(p).Encode(&progressLine{ID: id, Status: status})
|
|
| 202 |
-} |
|
| 203 |
-func (p *progressGenerator) detail(id, status string, current, total int64) {
|
|
| 204 |
- (*json.Encoder)(p).Encode(&progressLine{ID: id, Status: status, Detail: &progressDetail{Current: current, Total: total}})
|
|
| 205 |
-} |
|
| 206 |
-func (p *progressGenerator) err(msg string) {
|
|
| 207 |
- (*json.Encoder)(p).Encode(&progressLine{Error: msg})
|
|
| 208 |
-} |
| 209 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,45 +0,0 @@ |
| 1 |
-package imageprogress |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-// NewPullWriter creates a writer that periodically reports |
|
| 9 |
-// on pull progress of a Docker image. It only reports when the state of the |
|
| 10 |
-// different layers has changed and uses time thresholds to limit the |
|
| 11 |
-// rate of the reports. |
|
| 12 |
-func NewPullWriter(printFn func(string)) io.Writer {
|
|
| 13 |
- return newWriter(pullReporter(printFn), pullLayersChanged) |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func pullReporter(printFn func(string)) func(report) {
|
|
| 17 |
- extracting := false |
|
| 18 |
- return func(r report) {
|
|
| 19 |
- if extracting {
|
|
| 20 |
- return |
|
| 21 |
- } |
|
| 22 |
- if r.count(statusDownloading) == 0 && |
|
| 23 |
- r.count(statusPending) == 0 && |
|
| 24 |
- r.count(statusExtracting) > 0 {
|
|
| 25 |
- |
|
| 26 |
- printFn(fmt.Sprintf("Pulled %[1]d/%[1]d layers, 100%% complete", r.totalCount()))
|
|
| 27 |
- printFn("Extracting")
|
|
| 28 |
- extracting = true |
|
| 29 |
- return |
|
| 30 |
- } |
|
| 31 |
- |
|
| 32 |
- completeCount := r.count(statusComplete) + r.count(statusExtracting) |
|
| 33 |
- var pctComplete float32 = 0.0 |
|
| 34 |
- pctComplete += float32(completeCount) / float32(r.totalCount()) |
|
| 35 |
- pctComplete += float32(r.count(statusDownloading)) / float32(r.totalCount()) * r.percentProgress(statusDownloading) / 100.0 |
|
| 36 |
- pctComplete *= 100.0 |
|
| 37 |
- printFn(fmt.Sprintf("Pulled %d/%d layers, %.0f%% complete", completeCount, r.totalCount(), pctComplete))
|
|
| 38 |
- } |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-func pullLayersChanged(older, newer report) bool {
|
|
| 42 |
- olderCompleteCount := older.count(statusComplete) + older.count(statusExtracting) |
|
| 43 |
- newerCompleteCount := newer.count(statusComplete) + newer.count(statusExtracting) |
|
| 44 |
- return olderCompleteCount != newerCompleteCount |
|
| 45 |
-} |
| 46 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,29 +0,0 @@ |
| 1 |
-package imageprogress |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "io" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-// NewPushWriter creates a writer that periodically reports |
|
| 9 |
-// on push progress of a Docker image. It only reports when the state of the |
|
| 10 |
-// different layers has changed and uses time thresholds to limit the |
|
| 11 |
-// rate of the reports. |
|
| 12 |
-func NewPushWriter(printFn func(string)) io.Writer {
|
|
| 13 |
- return newWriter(pushReporter(printFn), pushLayersChanged) |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func pushReporter(printFn func(string)) func(report) {
|
|
| 17 |
- return func(r report) {
|
|
| 18 |
- var pctComplete float32 = 0.0 |
|
| 19 |
- pctComplete += float32(r.count(statusComplete)) / float32(r.totalCount()) |
|
| 20 |
- pctComplete += float32(r.count(statusPushing)) / float32(r.totalCount()) * r.percentProgress(statusPushing) / 100.0 |
|
| 21 |
- pctComplete *= 100.0 |
|
| 22 |
- |
|
| 23 |
- printFn(fmt.Sprintf("Pushed %d/%d layers, %.0f%% complete", r.count(statusComplete), r.totalCount(), pctComplete))
|
|
| 24 |
- } |
|
| 25 |
-} |
|
| 26 |
- |
|
| 27 |
-func pushLayersChanged(older, newer report) bool {
|
|
| 28 |
- return older.count(statusComplete) != newer.count(statusComplete) |
|
| 29 |
-} |
| 30 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,95 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "runtime" |
|
| 8 |
- "strings" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// isURL returns true if the string appears to be a URL. |
|
| 12 |
-func isURL(s string) bool {
|
|
| 13 |
- return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-// exportEnv creates an export statement for a shell that contains all of the |
|
| 17 |
-// provided environment. |
|
| 18 |
-func exportEnv(env []string) string {
|
|
| 19 |
- if len(env) == 0 {
|
|
| 20 |
- return "" |
|
| 21 |
- } |
|
| 22 |
- out := "export" |
|
| 23 |
- for _, e := range env {
|
|
| 24 |
- out += " " + bashQuote(e) |
|
| 25 |
- } |
|
| 26 |
- return out + "; " |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-// bashQuote escapes the provided string and surrounds it with double quotes. |
|
| 30 |
-// TODO: verify that these are all we have to escape. |
|
| 31 |
-func bashQuote(env string) string {
|
|
| 32 |
- out := []rune{'"'}
|
|
| 33 |
- for _, r := range env {
|
|
| 34 |
- switch r {
|
|
| 35 |
- case '$', '\\', '"': |
|
| 36 |
- out = append(out, '\\', r) |
|
| 37 |
- default: |
|
| 38 |
- out = append(out, r) |
|
| 39 |
- } |
|
| 40 |
- } |
|
| 41 |
- out = append(out, '"') |
|
| 42 |
- return string(out) |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-// hasEnvName returns true if the provided environment contains the named ENV var. |
|
| 46 |
-func hasEnvName(env []string, name string) bool {
|
|
| 47 |
- for _, e := range env {
|
|
| 48 |
- if strings.HasPrefix(e, name+"=") {
|
|
| 49 |
- return true |
|
| 50 |
- } |
|
| 51 |
- } |
|
| 52 |
- return false |
|
| 53 |
-} |
|
| 54 |
- |
|
| 55 |
-// platformSupports is a short-term function to give users a quality error |
|
| 56 |
-// message if a Dockerfile uses a command not supported on the platform. |
|
| 57 |
-func platformSupports(command string) error {
|
|
| 58 |
- if runtime.GOOS != "windows" {
|
|
| 59 |
- return nil |
|
| 60 |
- } |
|
| 61 |
- switch command {
|
|
| 62 |
- case "expose", "user", "stopsignal", "arg": |
|
| 63 |
- return fmt.Errorf("The daemon on this platform does not support the command '%s'", command)
|
|
| 64 |
- } |
|
| 65 |
- return nil |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-func handleJSONArgs(args []string, attributes map[string]bool) []string {
|
|
| 69 |
- if len(args) == 0 {
|
|
| 70 |
- return []string{}
|
|
| 71 |
- } |
|
| 72 |
- |
|
| 73 |
- if attributes != nil && attributes["json"] {
|
|
| 74 |
- return args |
|
| 75 |
- } |
|
| 76 |
- |
|
| 77 |
- // literal string command, not an exec array |
|
| 78 |
- return []string{strings.Join(args, " ")}
|
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-// makeAbsolute ensures that the provided path is absolute. |
|
| 82 |
-func makeAbsolute(dest, workingDir string) string {
|
|
| 83 |
- // Twiddle the destination when its a relative path - meaning, make it |
|
| 84 |
- // relative to the WORKINGDIR |
|
| 85 |
- if !filepath.IsAbs(dest) {
|
|
| 86 |
- hasSlash := strings.HasSuffix(dest, string(os.PathSeparator)) || strings.HasSuffix(dest, string(os.PathSeparator)+".") |
|
| 87 |
- dest = filepath.Join(string(os.PathSeparator), filepath.FromSlash(workingDir), dest) |
|
| 88 |
- |
|
| 89 |
- // Make sure we preserve any trailing slash |
|
| 90 |
- if hasSlash {
|
|
| 91 |
- dest += string(os.PathSeparator) |
|
| 92 |
- } |
|
| 93 |
- } |
|
| 94 |
- return dest |
|
| 95 |
-} |
| 96 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,314 +0,0 @@ |
| 1 |
-package builder |
|
| 2 |
- |
|
| 3 |
-// This will take a single word and an array of env variables and |
|
| 4 |
-// process all quotes (" and ') as well as $xxx and ${xxx} env variable
|
|
| 5 |
-// tokens. Tries to mimic bash shell process. |
|
| 6 |
-// It doesn't support all flavors of ${xx:...} formats but new ones can
|
|
| 7 |
-// be added by adding code to the "special ${} format processing" section
|
|
| 8 |
- |
|
| 9 |
-import ( |
|
| 10 |
- "fmt" |
|
| 11 |
- "strings" |
|
| 12 |
- "text/scanner" |
|
| 13 |
- "unicode" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-type shellWord struct {
|
|
| 17 |
- word string |
|
| 18 |
- scanner scanner.Scanner |
|
| 19 |
- envs []string |
|
| 20 |
- pos int |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-// ProcessWord will use the 'env' list of environment variables, |
|
| 24 |
-// and replace any env var references in 'word'. |
|
| 25 |
-func ProcessWord(word string, env []string) (string, error) {
|
|
| 26 |
- sw := &shellWord{
|
|
| 27 |
- word: word, |
|
| 28 |
- envs: env, |
|
| 29 |
- pos: 0, |
|
| 30 |
- } |
|
| 31 |
- sw.scanner.Init(strings.NewReader(word)) |
|
| 32 |
- word, _, err := sw.process() |
|
| 33 |
- return word, err |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-// ProcessWords will use the 'env' list of environment variables, |
|
| 37 |
-// and replace any env var references in 'word' then it will also |
|
| 38 |
-// return a slice of strings which represents the 'word' |
|
| 39 |
-// split up based on spaces - taking into account quotes. Note that |
|
| 40 |
-// this splitting is done **after** the env var substitutions are done. |
|
| 41 |
-// Note, each one is trimmed to remove leading and trailing spaces (unless |
|
| 42 |
-// they are quoted", but ProcessWord retains spaces between words. |
|
| 43 |
-func ProcessWords(word string, env []string) ([]string, error) {
|
|
| 44 |
- sw := &shellWord{
|
|
| 45 |
- word: word, |
|
| 46 |
- envs: env, |
|
| 47 |
- pos: 0, |
|
| 48 |
- } |
|
| 49 |
- sw.scanner.Init(strings.NewReader(word)) |
|
| 50 |
- _, words, err := sw.process() |
|
| 51 |
- return words, err |
|
| 52 |
-} |
|
| 53 |
- |
|
| 54 |
-func (sw *shellWord) process() (string, []string, error) {
|
|
| 55 |
- return sw.processStopOn(scanner.EOF) |
|
| 56 |
-} |
|
| 57 |
- |
|
| 58 |
-type wordsStruct struct {
|
|
| 59 |
- word string |
|
| 60 |
- words []string |
|
| 61 |
- inWord bool |
|
| 62 |
-} |
|
| 63 |
- |
|
| 64 |
-func (w *wordsStruct) addChar(ch rune) {
|
|
| 65 |
- if unicode.IsSpace(ch) && w.inWord {
|
|
| 66 |
- if len(w.word) != 0 {
|
|
| 67 |
- w.words = append(w.words, w.word) |
|
| 68 |
- w.word = "" |
|
| 69 |
- w.inWord = false |
|
| 70 |
- } |
|
| 71 |
- } else if !unicode.IsSpace(ch) {
|
|
| 72 |
- w.addRawChar(ch) |
|
| 73 |
- } |
|
| 74 |
-} |
|
| 75 |
- |
|
| 76 |
-func (w *wordsStruct) addRawChar(ch rune) {
|
|
| 77 |
- w.word += string(ch) |
|
| 78 |
- w.inWord = true |
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-func (w *wordsStruct) addString(str string) {
|
|
| 82 |
- var scan scanner.Scanner |
|
| 83 |
- scan.Init(strings.NewReader(str)) |
|
| 84 |
- for scan.Peek() != scanner.EOF {
|
|
| 85 |
- w.addChar(scan.Next()) |
|
| 86 |
- } |
|
| 87 |
-} |
|
| 88 |
- |
|
| 89 |
-func (w *wordsStruct) addRawString(str string) {
|
|
| 90 |
- w.word += str |
|
| 91 |
- w.inWord = true |
|
| 92 |
-} |
|
| 93 |
- |
|
| 94 |
-func (w *wordsStruct) getWords() []string {
|
|
| 95 |
- if len(w.word) > 0 {
|
|
| 96 |
- w.words = append(w.words, w.word) |
|
| 97 |
- |
|
| 98 |
- // Just in case we're called again by mistake |
|
| 99 |
- w.word = "" |
|
| 100 |
- w.inWord = false |
|
| 101 |
- } |
|
| 102 |
- return w.words |
|
| 103 |
-} |
|
| 104 |
- |
|
| 105 |
-// Process the word, starting at 'pos', and stop when we get to the |
|
| 106 |
-// end of the word or the 'stopChar' character |
|
| 107 |
-func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
|
|
| 108 |
- var result string |
|
| 109 |
- var words wordsStruct |
|
| 110 |
- |
|
| 111 |
- var charFuncMapping = map[rune]func() (string, error){
|
|
| 112 |
- '\'': sw.processSingleQuote, |
|
| 113 |
- '"': sw.processDoubleQuote, |
|
| 114 |
- '$': sw.processDollar, |
|
| 115 |
- } |
|
| 116 |
- |
|
| 117 |
- for sw.scanner.Peek() != scanner.EOF {
|
|
| 118 |
- ch := sw.scanner.Peek() |
|
| 119 |
- |
|
| 120 |
- if stopChar != scanner.EOF && ch == stopChar {
|
|
| 121 |
- sw.scanner.Next() |
|
| 122 |
- break |
|
| 123 |
- } |
|
| 124 |
- if fn, ok := charFuncMapping[ch]; ok {
|
|
| 125 |
- // Call special processing func for certain chars |
|
| 126 |
- tmp, err := fn() |
|
| 127 |
- if err != nil {
|
|
| 128 |
- return "", []string{}, err
|
|
| 129 |
- } |
|
| 130 |
- result += tmp |
|
| 131 |
- |
|
| 132 |
- if ch == rune('$') {
|
|
| 133 |
- words.addString(tmp) |
|
| 134 |
- } else {
|
|
| 135 |
- words.addRawString(tmp) |
|
| 136 |
- } |
|
| 137 |
- } else {
|
|
| 138 |
- // Not special, just add it to the result |
|
| 139 |
- ch = sw.scanner.Next() |
|
| 140 |
- |
|
| 141 |
- if ch == '\\' {
|
|
| 142 |
- // '\' escapes, except end of line |
|
| 143 |
- |
|
| 144 |
- ch = sw.scanner.Next() |
|
| 145 |
- |
|
| 146 |
- if ch == scanner.EOF {
|
|
| 147 |
- break |
|
| 148 |
- } |
|
| 149 |
- |
|
| 150 |
- words.addRawChar(ch) |
|
| 151 |
- } else {
|
|
| 152 |
- words.addChar(ch) |
|
| 153 |
- } |
|
| 154 |
- |
|
| 155 |
- result += string(ch) |
|
| 156 |
- } |
|
| 157 |
- } |
|
| 158 |
- |
|
| 159 |
- return result, words.getWords(), nil |
|
| 160 |
-} |
|
| 161 |
- |
|
| 162 |
-func (sw *shellWord) processSingleQuote() (string, error) {
|
|
| 163 |
- // All chars between single quotes are taken as-is |
|
| 164 |
- // Note, you can't escape ' |
|
| 165 |
- var result string |
|
| 166 |
- |
|
| 167 |
- sw.scanner.Next() |
|
| 168 |
- |
|
| 169 |
- for {
|
|
| 170 |
- ch := sw.scanner.Next() |
|
| 171 |
- if ch == '\'' || ch == scanner.EOF {
|
|
| 172 |
- break |
|
| 173 |
- } |
|
| 174 |
- result += string(ch) |
|
| 175 |
- } |
|
| 176 |
- |
|
| 177 |
- return result, nil |
|
| 178 |
-} |
|
| 179 |
- |
|
| 180 |
-func (sw *shellWord) processDoubleQuote() (string, error) {
|
|
| 181 |
- // All chars up to the next " are taken as-is, even ', except any $ chars |
|
| 182 |
- // But you can escape " with a \ |
|
| 183 |
- var result string |
|
| 184 |
- |
|
| 185 |
- sw.scanner.Next() |
|
| 186 |
- |
|
| 187 |
- for sw.scanner.Peek() != scanner.EOF {
|
|
| 188 |
- ch := sw.scanner.Peek() |
|
| 189 |
- if ch == '"' {
|
|
| 190 |
- sw.scanner.Next() |
|
| 191 |
- break |
|
| 192 |
- } |
|
| 193 |
- if ch == '$' {
|
|
| 194 |
- tmp, err := sw.processDollar() |
|
| 195 |
- if err != nil {
|
|
| 196 |
- return "", err |
|
| 197 |
- } |
|
| 198 |
- result += tmp |
|
| 199 |
- } else {
|
|
| 200 |
- ch = sw.scanner.Next() |
|
| 201 |
- if ch == '\\' {
|
|
| 202 |
- chNext := sw.scanner.Peek() |
|
| 203 |
- |
|
| 204 |
- if chNext == scanner.EOF {
|
|
| 205 |
- // Ignore \ at end of word |
|
| 206 |
- continue |
|
| 207 |
- } |
|
| 208 |
- |
|
| 209 |
- if chNext == '"' || chNext == '$' {
|
|
| 210 |
- // \" and \$ can be escaped, all other \'s are left as-is |
|
| 211 |
- ch = sw.scanner.Next() |
|
| 212 |
- } |
|
| 213 |
- } |
|
| 214 |
- result += string(ch) |
|
| 215 |
- } |
|
| 216 |
- } |
|
| 217 |
- |
|
| 218 |
- return result, nil |
|
| 219 |
-} |
|
| 220 |
- |
|
| 221 |
-func (sw *shellWord) processDollar() (string, error) {
|
|
| 222 |
- sw.scanner.Next() |
|
| 223 |
- ch := sw.scanner.Peek() |
|
| 224 |
- if ch == '{' {
|
|
| 225 |
- sw.scanner.Next() |
|
| 226 |
- name := sw.processName() |
|
| 227 |
- ch = sw.scanner.Peek() |
|
| 228 |
- if ch == '}' {
|
|
| 229 |
- // Normal ${xx} case
|
|
| 230 |
- sw.scanner.Next() |
|
| 231 |
- return sw.getEnv(name), nil |
|
| 232 |
- } |
|
| 233 |
- if ch == ':' {
|
|
| 234 |
- // Special ${xx:...} format processing
|
|
| 235 |
- // Yes it allows for recursive $'s in the ... spot |
|
| 236 |
- |
|
| 237 |
- sw.scanner.Next() // skip over : |
|
| 238 |
- modifier := sw.scanner.Next() |
|
| 239 |
- |
|
| 240 |
- word, _, err := sw.processStopOn('}')
|
|
| 241 |
- if err != nil {
|
|
| 242 |
- return "", err |
|
| 243 |
- } |
|
| 244 |
- |
|
| 245 |
- // Grab the current value of the variable in question so we |
|
| 246 |
- // can use to to determine what to do based on the modifier |
|
| 247 |
- newValue := sw.getEnv(name) |
|
| 248 |
- |
|
| 249 |
- switch modifier {
|
|
| 250 |
- case '+': |
|
| 251 |
- if newValue != "" {
|
|
| 252 |
- newValue = word |
|
| 253 |
- } |
|
| 254 |
- return newValue, nil |
|
| 255 |
- |
|
| 256 |
- case '-': |
|
| 257 |
- if newValue == "" {
|
|
| 258 |
- newValue = word |
|
| 259 |
- } |
|
| 260 |
- return newValue, nil |
|
| 261 |
- |
|
| 262 |
- default: |
|
| 263 |
- return "", fmt.Errorf("Unsupported modifier (%c) in substitution: %s", modifier, sw.word)
|
|
| 264 |
- } |
|
| 265 |
- } |
|
| 266 |
- return "", fmt.Errorf("Missing ':' in substitution: %s", sw.word)
|
|
| 267 |
- } |
|
| 268 |
- // $xxx case |
|
| 269 |
- name := sw.processName() |
|
| 270 |
- if name == "" {
|
|
| 271 |
- return "$", nil |
|
| 272 |
- } |
|
| 273 |
- return sw.getEnv(name), nil |
|
| 274 |
-} |
|
| 275 |
- |
|
| 276 |
-func (sw *shellWord) processName() string {
|
|
| 277 |
- // Read in a name (alphanumeric or _) |
|
| 278 |
- // If it starts with a numeric then just return $# |
|
| 279 |
- var name string |
|
| 280 |
- |
|
| 281 |
- for sw.scanner.Peek() != scanner.EOF {
|
|
| 282 |
- ch := sw.scanner.Peek() |
|
| 283 |
- if len(name) == 0 && unicode.IsDigit(ch) {
|
|
| 284 |
- ch = sw.scanner.Next() |
|
| 285 |
- return string(ch) |
|
| 286 |
- } |
|
| 287 |
- if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' {
|
|
| 288 |
- break |
|
| 289 |
- } |
|
| 290 |
- ch = sw.scanner.Next() |
|
| 291 |
- name += string(ch) |
|
| 292 |
- } |
|
| 293 |
- |
|
| 294 |
- return name |
|
| 295 |
-} |
|
| 296 |
- |
|
| 297 |
-func (sw *shellWord) getEnv(name string) string {
|
|
| 298 |
- for _, env := range sw.envs {
|
|
| 299 |
- i := strings.Index(env, "=") |
|
| 300 |
- if i < 0 {
|
|
| 301 |
- if name == env {
|
|
| 302 |
- // Should probably never get here, but just in case treat |
|
| 303 |
- // it like "var" and "var=" are the same |
|
| 304 |
- return "" |
|
| 305 |
- } |
|
| 306 |
- continue |
|
| 307 |
- } |
|
| 308 |
- if name != env[:i] {
|
|
| 309 |
- continue |
|
| 310 |
- } |
|
| 311 |
- return env[i+1:] |
|
| 312 |
- } |
|
| 313 |
- return "" |
|
| 314 |
-} |
| 3 | 2 |
deleted file mode 100644 |
| ... | ... |
@@ -1,37 +0,0 @@ |
| 1 |
-// Package signal provides helper functions for dealing with signals across |
|
| 2 |
-// various operating systems. |
|
| 3 |
-package signal |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- "strconv" |
|
| 8 |
- "strings" |
|
| 9 |
- "syscall" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-// ParseSignal translates a string to a valid syscall signal. |
|
| 13 |
-// It returns an error if the signal map doesn't include the given signal. |
|
| 14 |
-func ParseSignal(rawSignal string) (syscall.Signal, error) {
|
|
| 15 |
- s, err := strconv.Atoi(rawSignal) |
|
| 16 |
- if err == nil {
|
|
| 17 |
- if s == 0 {
|
|
| 18 |
- return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
|
|
| 19 |
- } |
|
| 20 |
- return syscall.Signal(s), nil |
|
| 21 |
- } |
|
| 22 |
- signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] |
|
| 23 |
- if !ok {
|
|
| 24 |
- return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
|
|
| 25 |
- } |
|
| 26 |
- return signal, nil |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-// ValidSignalForPlatform returns true if a signal is valid on the platform |
|
| 30 |
-func ValidSignalForPlatform(sig syscall.Signal) bool {
|
|
| 31 |
- for _, v := range SignalMap {
|
|
| 32 |
- if v == sig {
|
|
| 33 |
- return true |
|
| 34 |
- } |
|
| 35 |
- } |
|
| 36 |
- return false |
|
| 37 |
-} |
| 38 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,41 +0,0 @@ |
| 1 |
-package signal |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "syscall" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// SignalMap is a map of Darwin signals. |
|
| 8 |
-var SignalMap = map[string]syscall.Signal{
|
|
| 9 |
- "ABRT": syscall.SIGABRT, |
|
| 10 |
- "ALRM": syscall.SIGALRM, |
|
| 11 |
- "BUG": syscall.SIGBUS, |
|
| 12 |
- "CHLD": syscall.SIGCHLD, |
|
| 13 |
- "CONT": syscall.SIGCONT, |
|
| 14 |
- "EMT": syscall.SIGEMT, |
|
| 15 |
- "FPE": syscall.SIGFPE, |
|
| 16 |
- "HUP": syscall.SIGHUP, |
|
| 17 |
- "ILL": syscall.SIGILL, |
|
| 18 |
- "INFO": syscall.SIGINFO, |
|
| 19 |
- "INT": syscall.SIGINT, |
|
| 20 |
- "IO": syscall.SIGIO, |
|
| 21 |
- "IOT": syscall.SIGIOT, |
|
| 22 |
- "KILL": syscall.SIGKILL, |
|
| 23 |
- "PIPE": syscall.SIGPIPE, |
|
| 24 |
- "PROF": syscall.SIGPROF, |
|
| 25 |
- "QUIT": syscall.SIGQUIT, |
|
| 26 |
- "SEGV": syscall.SIGSEGV, |
|
| 27 |
- "STOP": syscall.SIGSTOP, |
|
| 28 |
- "SYS": syscall.SIGSYS, |
|
| 29 |
- "TERM": syscall.SIGTERM, |
|
| 30 |
- "TRAP": syscall.SIGTRAP, |
|
| 31 |
- "TSTP": syscall.SIGTSTP, |
|
| 32 |
- "TTIN": syscall.SIGTTIN, |
|
| 33 |
- "TTOU": syscall.SIGTTOU, |
|
| 34 |
- "URG": syscall.SIGURG, |
|
| 35 |
- "USR1": syscall.SIGUSR1, |
|
| 36 |
- "USR2": syscall.SIGUSR2, |
|
| 37 |
- "VTALRM": syscall.SIGVTALRM, |
|
| 38 |
- "WINCH": syscall.SIGWINCH, |
|
| 39 |
- "XCPU": syscall.SIGXCPU, |
|
| 40 |
- "XFSZ": syscall.SIGXFSZ, |
|
| 41 |
-} |
| 42 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,43 +0,0 @@ |
| 1 |
-package signal |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "syscall" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-// SignalMap is a map of FreeBSD signals. |
|
| 8 |
-var SignalMap = map[string]syscall.Signal{
|
|
| 9 |
- "ABRT": syscall.SIGABRT, |
|
| 10 |
- "ALRM": syscall.SIGALRM, |
|
| 11 |
- "BUF": syscall.SIGBUS, |
|
| 12 |
- "CHLD": syscall.SIGCHLD, |
|
| 13 |
- "CONT": syscall.SIGCONT, |
|
| 14 |
- "EMT": syscall.SIGEMT, |
|
| 15 |
- "FPE": syscall.SIGFPE, |
|
| 16 |
- "HUP": syscall.SIGHUP, |
|
| 17 |
- "ILL": syscall.SIGILL, |
|
| 18 |
- "INFO": syscall.SIGINFO, |
|
| 19 |
- "INT": syscall.SIGINT, |
|
| 20 |
- "IO": syscall.SIGIO, |
|
| 21 |
- "IOT": syscall.SIGIOT, |
|
| 22 |
- "KILL": syscall.SIGKILL, |
|
| 23 |
- "LWP": syscall.SIGLWP, |
|
| 24 |
- "PIPE": syscall.SIGPIPE, |
|
| 25 |
- "PROF": syscall.SIGPROF, |
|
| 26 |
- "QUIT": syscall.SIGQUIT, |
|
| 27 |
- "SEGV": syscall.SIGSEGV, |
|
| 28 |
- "STOP": syscall.SIGSTOP, |
|
| 29 |
- "SYS": syscall.SIGSYS, |
|
| 30 |
- "TERM": syscall.SIGTERM, |
|
| 31 |
- "THR": syscall.SIGTHR, |
|
| 32 |
- "TRAP": syscall.SIGTRAP, |
|
| 33 |
- "TSTP": syscall.SIGTSTP, |
|
| 34 |
- "TTIN": syscall.SIGTTIN, |
|
| 35 |
- "TTOU": syscall.SIGTTOU, |
|
| 36 |
- "URG": syscall.SIGURG, |
|
| 37 |
- "USR1": syscall.SIGUSR1, |
|
| 38 |
- "USR2": syscall.SIGUSR2, |
|
| 39 |
- "VTALRM": syscall.SIGVTALRM, |
|
| 40 |
- "WINCH": syscall.SIGWINCH, |
|
| 41 |
- "XCPU": syscall.SIGXCPU, |
|
| 42 |
- "XFSZ": syscall.SIGXFSZ, |
|
| 43 |
-} |
| 44 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,80 +0,0 @@ |
| 1 |
-package signal |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "syscall" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-const ( |
|
| 8 |
- sigrtmin = 34 |
|
| 9 |
- sigrtmax = 64 |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-// SignalMap is a map of Linux signals. |
|
| 13 |
-var SignalMap = map[string]syscall.Signal{
|
|
| 14 |
- "ABRT": syscall.SIGABRT, |
|
| 15 |
- "ALRM": syscall.SIGALRM, |
|
| 16 |
- "BUS": syscall.SIGBUS, |
|
| 17 |
- "CHLD": syscall.SIGCHLD, |
|
| 18 |
- "CLD": syscall.SIGCLD, |
|
| 19 |
- "CONT": syscall.SIGCONT, |
|
| 20 |
- "FPE": syscall.SIGFPE, |
|
| 21 |
- "HUP": syscall.SIGHUP, |
|
| 22 |
- "ILL": syscall.SIGILL, |
|
| 23 |
- "INT": syscall.SIGINT, |
|
| 24 |
- "IO": syscall.SIGIO, |
|
| 25 |
- "IOT": syscall.SIGIOT, |
|
| 26 |
- "KILL": syscall.SIGKILL, |
|
| 27 |
- "PIPE": syscall.SIGPIPE, |
|
| 28 |
- "POLL": syscall.SIGPOLL, |
|
| 29 |
- "PROF": syscall.SIGPROF, |
|
| 30 |
- "PWR": syscall.SIGPWR, |
|
| 31 |
- "QUIT": syscall.SIGQUIT, |
|
| 32 |
- "SEGV": syscall.SIGSEGV, |
|
| 33 |
- "STKFLT": syscall.SIGSTKFLT, |
|
| 34 |
- "STOP": syscall.SIGSTOP, |
|
| 35 |
- "SYS": syscall.SIGSYS, |
|
| 36 |
- "TERM": syscall.SIGTERM, |
|
| 37 |
- "TRAP": syscall.SIGTRAP, |
|
| 38 |
- "TSTP": syscall.SIGTSTP, |
|
| 39 |
- "TTIN": syscall.SIGTTIN, |
|
| 40 |
- "TTOU": syscall.SIGTTOU, |
|
| 41 |
- "UNUSED": syscall.SIGUNUSED, |
|
| 42 |
- "URG": syscall.SIGURG, |
|
| 43 |
- "USR1": syscall.SIGUSR1, |
|
| 44 |
- "USR2": syscall.SIGUSR2, |
|
| 45 |
- "VTALRM": syscall.SIGVTALRM, |
|
| 46 |
- "WINCH": syscall.SIGWINCH, |
|
| 47 |
- "XCPU": syscall.SIGXCPU, |
|
| 48 |
- "XFSZ": syscall.SIGXFSZ, |
|
| 49 |
- "RTMIN": sigrtmin, |
|
| 50 |
- "RTMIN+1": sigrtmin + 1, |
|
| 51 |
- "RTMIN+2": sigrtmin + 2, |
|
| 52 |
- "RTMIN+3": sigrtmin + 3, |
|
| 53 |
- "RTMIN+4": sigrtmin + 4, |
|
| 54 |
- "RTMIN+5": sigrtmin + 5, |
|
| 55 |
- "RTMIN+6": sigrtmin + 6, |
|
| 56 |
- "RTMIN+7": sigrtmin + 7, |
|
| 57 |
- "RTMIN+8": sigrtmin + 8, |
|
| 58 |
- "RTMIN+9": sigrtmin + 9, |
|
| 59 |
- "RTMIN+10": sigrtmin + 10, |
|
| 60 |
- "RTMIN+11": sigrtmin + 11, |
|
| 61 |
- "RTMIN+12": sigrtmin + 12, |
|
| 62 |
- "RTMIN+13": sigrtmin + 13, |
|
| 63 |
- "RTMIN+14": sigrtmin + 14, |
|
| 64 |
- "RTMIN+15": sigrtmin + 15, |
|
| 65 |
- "RTMAX-14": sigrtmax - 14, |
|
| 66 |
- "RTMAX-13": sigrtmax - 13, |
|
| 67 |
- "RTMAX-12": sigrtmax - 12, |
|
| 68 |
- "RTMAX-11": sigrtmax - 11, |
|
| 69 |
- "RTMAX-10": sigrtmax - 10, |
|
| 70 |
- "RTMAX-9": sigrtmax - 9, |
|
| 71 |
- "RTMAX-8": sigrtmax - 8, |
|
| 72 |
- "RTMAX-7": sigrtmax - 7, |
|
| 73 |
- "RTMAX-6": sigrtmax - 6, |
|
| 74 |
- "RTMAX-5": sigrtmax - 5, |
|
| 75 |
- "RTMAX-4": sigrtmax - 4, |
|
| 76 |
- "RTMAX-3": sigrtmax - 3, |
|
| 77 |
- "RTMAX-2": sigrtmax - 2, |
|
| 78 |
- "RTMAX-1": sigrtmax - 1, |
|
| 79 |
- "RTMAX": sigrtmax, |
|
| 80 |
-} |
| 81 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,19 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package signal |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "syscall" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// Signals used in api/client (no windows equivalent, use |
|
| 10 |
-// invalid signals so they don't get handled) |
|
| 11 |
- |
|
| 12 |
-const ( |
|
| 13 |
- // SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted. |
|
| 14 |
- SIGCHLD = syscall.SIGCHLD |
|
| 15 |
- // SIGWINCH is a signal sent to a process when its controlling terminal changes its size |
|
| 16 |
- SIGWINCH = syscall.SIGWINCH |
|
| 17 |
- // DefaultStopSignal is the syscall signal used to stop a container in unix systems. |
|
| 18 |
- DefaultStopSignal = "SIGTERM" |
|
| 19 |
-) |
| 11 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,27 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-package signal |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "syscall" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// Signals used in api/client (no windows equivalent, use |
|
| 10 |
-// invalid signals so they don't get handled) |
|
| 11 |
-const ( |
|
| 12 |
- SIGCHLD = syscall.Signal(0xff) |
|
| 13 |
- SIGWINCH = syscall.Signal(0xff) |
|
| 14 |
- // DefaultStopSignal is the syscall signal used to stop a container in windows systems. |
|
| 15 |
- DefaultStopSignal = "15" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-// SignalMap is a map of "supported" signals. As per the comment in GOLang's |
|
| 19 |
-// ztypes_windows.go: "More invented values for signals". Windows doesn't |
|
| 20 |
-// really support signals in any way, shape or form that Unix does. |
|
| 21 |
-// |
|
| 22 |
-// We have these so that docker kill can be used to gracefully (TERM) and |
|
| 23 |
-// forcibly (KILL) terminate a container on Windows. |
|
| 24 |
-var SignalMap = map[string]syscall.Signal{
|
|
| 25 |
- "KILL": syscall.SIGKILL, |
|
| 26 |
- "TERM": syscall.SIGTERM, |
|
| 27 |
-} |
| 28 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,30 +0,0 @@ |
| 1 |
-package strslice |
|
| 2 |
- |
|
| 3 |
-import "encoding/json" |
|
| 4 |
- |
|
| 5 |
-// StrSlice represents a string or an array of strings. |
|
| 6 |
-// We need to override the json decoder to accept both options. |
|
| 7 |
-type StrSlice []string |
|
| 8 |
- |
|
| 9 |
-// UnmarshalJSON decodes the byte slice whether it's a string or an array of |
|
| 10 |
-// strings. This method is needed to implement json.Unmarshaler. |
|
| 11 |
-func (e *StrSlice) UnmarshalJSON(b []byte) error {
|
|
| 12 |
- if len(b) == 0 {
|
|
| 13 |
- // With no input, we preserve the existing value by returning nil and |
|
| 14 |
- // leaving the target alone. This allows defining default values for |
|
| 15 |
- // the type. |
|
| 16 |
- return nil |
|
| 17 |
- } |
|
| 18 |
- |
|
| 19 |
- p := make([]string, 0, 1) |
|
| 20 |
- if err := json.Unmarshal(b, &p); err != nil {
|
|
| 21 |
- var s string |
|
| 22 |
- if err := json.Unmarshal(b, &s); err != nil {
|
|
| 23 |
- return err |
|
| 24 |
- } |
|
| 25 |
- p = append(p, s) |
|
| 26 |
- } |
|
| 27 |
- |
|
| 28 |
- *e = p |
|
| 29 |
- return nil |
|
| 30 |
-} |
| 31 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,11 +0,0 @@ |
| 1 |
-FROM busybox |
|
| 2 |
-ADD https://github.com/openshift/origin/raw/master/README.md README.md |
|
| 3 |
-USER 1001 |
|
| 4 |
-ADD https://github.com/openshift/origin/raw/master/LICENSE . |
|
| 5 |
-ADD https://github.com/openshift/origin/raw/master/LICENSE A |
|
| 6 |
-ADD https://github.com/openshift/origin/raw/master/LICENSE ./a |
|
| 7 |
-USER root |
|
| 8 |
-RUN mkdir ./b |
|
| 9 |
-ADD https://github.com/openshift/origin/raw/master/LICENSE ./b/a |
|
| 10 |
-ADD https://github.com/openshift/origin/raw/master/LICENSE ./b/. |
|
| 11 |
-ADD https://github.com/openshift/ruby-hello-world/archive/master.zip /tmp/ |
| 12 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,48 +0,0 @@ |
| 1 |
-FROM busybox |
|
| 2 |
- |
|
| 3 |
-MAINTAINER docker <docker@docker.io> |
|
| 4 |
- |
|
| 5 |
-ONBUILD RUN ["echo", "test"] |
|
| 6 |
-ONBUILD RUN echo test |
|
| 7 |
-ONBUILD COPY . / |
|
| 8 |
- |
|
| 9 |
- |
|
| 10 |
-# RUN Commands \ |
|
| 11 |
-# linebreak in comment \ |
|
| 12 |
-RUN ["ls", "-la"] |
|
| 13 |
-RUN ["echo", "'1234'"] |
|
| 14 |
-RUN echo "1234" |
|
| 15 |
-RUN echo 1234 |
|
| 16 |
-RUN echo '1234' && \ |
|
| 17 |
- echo "456" && \ |
|
| 18 |
- echo 789 |
|
| 19 |
-RUN sh -c 'echo root:testpass \ |
|
| 20 |
- > /tmp/passwd' |
|
| 21 |
-RUN mkdir -p /test /test2 /test3/test |
|
| 22 |
- |
|
| 23 |
-# ENV \ |
|
| 24 |
-ENV SCUBA 1 DUBA 3 |
|
| 25 |
-ENV SCUBA "1 DUBA 3" |
|
| 26 |
- |
|
| 27 |
-# CMD \ |
|
| 28 |
-CMD ["echo", "test"] |
|
| 29 |
-CMD echo test |
|
| 30 |
-CMD echo "test" |
|
| 31 |
-CMD echo 'test' |
|
| 32 |
-CMD echo 'test' | wc - |
|
| 33 |
- |
|
| 34 |
-#EXPOSE\ |
|
| 35 |
-EXPOSE 3000 |
|
| 36 |
-EXPOSE 9000 5000 6000 |
|
| 37 |
- |
|
| 38 |
-USER docker |
|
| 39 |
-USER docker:root |
|
| 40 |
- |
|
| 41 |
-VOLUME ["/test"] |
|
| 42 |
-VOLUME ["/test", "/test2"] |
|
| 43 |
-VOLUME /test3 |
|
| 44 |
- |
|
| 45 |
-WORKDIR /test |
|
| 46 |
- |
|
| 47 |
-ADD . / |
|
| 48 |
-COPY . copy |
|
| 49 | 1 |
\ No newline at end of file |
| 50 | 2 |
deleted file mode 100644 |
| ... | ... |
@@ -1,23 +0,0 @@ |
| 1 |
-FROM busybox |
|
| 2 |
-ENV name value |
|
| 3 |
-ENV name=value |
|
| 4 |
-ENV name=value name2=value2 |
|
| 5 |
-ENV name="value value1" |
|
| 6 |
-ENV name=value\ value2 |
|
| 7 |
-ENV name="value'quote space'value2" |
|
| 8 |
-ENV name='value"double quote"value2' |
|
| 9 |
-ENV name=value\ value2 name2=value2\ value3 |
|
| 10 |
-ENV name="a\"b" |
|
| 11 |
-ENV name="a\'b" |
|
| 12 |
-ENV name='a\'b' |
|
| 13 |
-ENV name='a\'b'' |
|
| 14 |
-ENV name='a\"b' |
|
| 15 |
-ENV name="''" |
|
| 16 |
-# don't put anything after the next line - it must be the last line of the |
|
| 17 |
-# Dockerfile and it must end with \ |
|
| 18 |
-ENV name=value \ |
|
| 19 |
- name1=value1 \ |
|
| 20 |
- name2="value2a \ |
|
| 21 |
- value2b" \ |
|
| 22 |
- name3="value3a\n\"value3b\"" \ |
|
| 23 |
- name4="value4a\\nvalue4b" \ |
|
| 24 | 1 |
\ No newline at end of file |