github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/bindings/images/build.go (about) 1 package images 2 3 import ( 4 "archive/tar" 5 "context" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "os" 12 "path/filepath" 13 "regexp" 14 "strconv" 15 "strings" 16 17 "github.com/containers/buildah" 18 "github.com/containers/podman/v2/pkg/auth" 19 "github.com/containers/podman/v2/pkg/bindings" 20 "github.com/containers/podman/v2/pkg/domain/entities" 21 "github.com/docker/go-units" 22 "github.com/hashicorp/go-multierror" 23 jsoniter "github.com/json-iterator/go" 24 "github.com/pkg/errors" 25 "github.com/sirupsen/logrus" 26 ) 27 28 // Build creates an image using a containerfile reference 29 func Build(ctx context.Context, containerFiles []string, options entities.BuildOptions) (*entities.BuildReport, error) { 30 params := url.Values{} 31 32 if t := options.Output; len(t) > 0 { 33 params.Set("t", t) 34 } 35 for _, tag := range options.AdditionalTags { 36 params.Add("t", tag) 37 } 38 if options.Quiet { 39 params.Set("q", "1") 40 } 41 if options.NoCache { 42 params.Set("nocache", "1") 43 } 44 // TODO cachefrom 45 if options.PullPolicy == buildah.PullAlways { 46 params.Set("pull", "1") 47 } 48 if options.RemoveIntermediateCtrs { 49 params.Set("rm", "1") 50 } 51 if options.ForceRmIntermediateCtrs { 52 params.Set("forcerm", "1") 53 } 54 if mem := options.CommonBuildOpts.Memory; mem > 0 { 55 params.Set("memory", strconv.Itoa(int(mem))) 56 } 57 if memSwap := options.CommonBuildOpts.MemorySwap; memSwap > 0 { 58 params.Set("memswap", strconv.Itoa(int(memSwap))) 59 } 60 if cpuShares := options.CommonBuildOpts.CPUShares; cpuShares > 0 { 61 params.Set("cpushares", strconv.Itoa(int(cpuShares))) 62 } 63 if cpuSetCpus := options.CommonBuildOpts.CPUSetCPUs; len(cpuSetCpus) > 0 { 64 params.Set("cpusetcpus", cpuSetCpus) 65 } 66 if cpuPeriod := options.CommonBuildOpts.CPUPeriod; cpuPeriod > 0 { 67 params.Set("cpuperiod", strconv.Itoa(int(cpuPeriod))) 68 } 69 if cpuQuota := options.CommonBuildOpts.CPUQuota; cpuQuota > 0 { 70 params.Set("cpuquota", strconv.Itoa(int(cpuQuota))) 71 } 72 if buildArgs := options.Args; len(buildArgs) > 0 { 73 bArgs, err := jsoniter.MarshalToString(buildArgs) 74 if err != nil { 75 return nil, err 76 } 77 params.Set("buildargs", bArgs) 78 } 79 if shmSize := options.CommonBuildOpts.ShmSize; len(shmSize) > 0 { 80 shmBytes, err := units.RAMInBytes(shmSize) 81 if err != nil { 82 return nil, err 83 } 84 params.Set("shmsize", strconv.Itoa(int(shmBytes))) 85 } 86 if options.Squash { 87 params.Set("squash", "1") 88 } 89 if labels := options.Labels; len(labels) > 0 { 90 l, err := jsoniter.MarshalToString(labels) 91 if err != nil { 92 return nil, err 93 } 94 params.Set("labels", l) 95 } 96 if options.CommonBuildOpts.HTTPProxy { 97 params.Set("httpproxy", "1") 98 } 99 100 var ( 101 headers map[string]string 102 err error 103 ) 104 if options.SystemContext == nil { 105 headers, err = auth.Header(options.SystemContext, auth.XRegistryConfigHeader, "", "", "") 106 } else { 107 if options.SystemContext.DockerAuthConfig != nil { 108 headers, err = auth.Header(options.SystemContext, auth.XRegistryAuthHeader, options.SystemContext.AuthFilePath, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password) 109 } else { 110 headers, err = auth.Header(options.SystemContext, auth.XRegistryConfigHeader, options.SystemContext.AuthFilePath, "", "") 111 } 112 } 113 if err != nil { 114 return nil, err 115 } 116 117 stdout := io.Writer(os.Stdout) 118 if options.Out != nil { 119 stdout = options.Out 120 } 121 122 // TODO network? 123 124 var platform string 125 if OS := options.OS; len(OS) > 0 { 126 platform += OS 127 } 128 if arch := options.Architecture; len(arch) > 0 { 129 platform += "/" + arch 130 } 131 if len(platform) > 0 { 132 params.Set("platform", platform) 133 } 134 135 entries := make([]string, len(containerFiles)) 136 copy(entries, containerFiles) 137 entries = append(entries, options.ContextDirectory) 138 tarfile, err := nTar(entries...) 139 if err != nil { 140 return nil, err 141 } 142 defer tarfile.Close() 143 params.Set("dockerfile", filepath.Base(containerFiles[0])) 144 145 conn, err := bindings.GetClient(ctx) 146 if err != nil { 147 return nil, err 148 } 149 response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params, headers) 150 if err != nil { 151 return nil, err 152 } 153 defer response.Body.Close() 154 155 if !response.IsSuccess() { 156 return nil, response.Process(err) 157 } 158 159 body := response.Body.(io.Reader) 160 if logrus.IsLevelEnabled(logrus.DebugLevel) { 161 if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { 162 if keep, _ := strconv.ParseBool(v); keep { 163 t, _ := ioutil.TempFile("", "build_*_client") 164 defer t.Close() 165 body = io.TeeReader(response.Body, t) 166 } 167 } 168 } 169 170 dec := json.NewDecoder(body) 171 re := regexp.MustCompile(`[0-9a-f]{12}`) 172 173 var id string 174 for { 175 var s struct { 176 Stream string `json:"stream,omitempty"` 177 Error string `json:"error,omitempty"` 178 } 179 if err := dec.Decode(&s); err != nil { 180 if errors.Is(err, io.EOF) { 181 return &entities.BuildReport{ID: id}, nil 182 } 183 s.Error = err.Error() + "\n" 184 } 185 186 switch { 187 case s.Stream != "": 188 stdout.Write([]byte(s.Stream)) 189 if re.Match([]byte(s.Stream)) { 190 id = strings.TrimSuffix(s.Stream, "\n") 191 } 192 case s.Error != "": 193 return nil, errors.New(s.Error) 194 default: 195 return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input") 196 } 197 } 198 } 199 200 func nTar(sources ...string) (io.ReadCloser, error) { 201 if len(sources) == 0 { 202 return nil, errors.New("No source(s) provided for build") 203 } 204 205 pr, pw := io.Pipe() 206 tw := tar.NewWriter(pw) 207 208 var merr error 209 go func() { 210 defer pw.Close() 211 defer tw.Close() 212 213 for _, src := range sources { 214 s := src 215 err := filepath.Walk(s, func(path string, info os.FileInfo, err error) error { 216 if err != nil { 217 return err 218 } 219 if !info.Mode().IsRegular() || path == s { 220 return nil 221 } 222 223 f, lerr := os.Open(path) 224 if lerr != nil { 225 return lerr 226 } 227 228 name := strings.TrimPrefix(path, s+string(filepath.Separator)) 229 hdr, lerr := tar.FileInfoHeader(info, name) 230 if lerr != nil { 231 f.Close() 232 return lerr 233 } 234 hdr.Name = name 235 if lerr := tw.WriteHeader(hdr); lerr != nil { 236 f.Close() 237 return lerr 238 } 239 240 _, cerr := io.Copy(tw, f) 241 f.Close() 242 return cerr 243 }) 244 merr = multierror.Append(merr, err) 245 } 246 }() 247 return pr, merr 248 }