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  }