build.go 3.9 KB
Newer Older
J
jingxiaolu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Copyright (c) Huawei Technologies Co., Ltd. 2020. All rights reserved.
// isula-build licensed under the Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//     http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
// PURPOSE.
// See the Mulan PSL v2 for more details.
// Author: iSula Team
// Create: 2020-01-20
// Description: This file is "build" command for backend

package daemon

import (
17
	"bufio"
J
jingxiaolu 已提交
18
	"context"
19
	"io"
J
jingxiaolu 已提交
20
	"io/ioutil"
21
	"os"
J
jingxiaolu 已提交
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

	"github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"

	constant "isula.org/isula-build"
	pb "isula.org/isula-build/api/services"
	"isula.org/isula-build/exporter"
	"isula.org/isula-build/util"
)

// Build receives a build request and build an image
func (b *Backend) Build(req *pb.BuildRequest, stream pb.Control_BuildServer) (err error) { // nolint:gocyclo
	logrus.WithFields(logrus.Fields{
		"BuildType": req.GetBuildType(),
		"BuildID":   req.GetBuildID(),
	}).Info("BuildRequest received")

39
	ctx := context.WithValue(stream.Context(), util.LogFieldKey(util.LogKeySessionID), req.BuildID)
J
jingxiaolu 已提交
40 41 42 43 44 45 46 47 48 49 50 51 52 53
	builder, err := b.daemon.NewBuilder(ctx, req)
	if err != nil {
		return err
	}

	defer func() {
		if cerr := builder.CleanResources(); cerr != nil {
			logrus.Warnf("defer builder clean build resources failed: %v", cerr)
		}
		b.daemon.deleteBuilder(req.BuildID)
		b.deleteStatus(req.BuildID)
	}()

	var (
54 55 56 57 58
		f       *os.File
		length  int
		imageID string
		eg      *errgroup.Group
		errC    = make(chan error, 1)
J
jingxiaolu 已提交
59 60 61 62 63 64
	)

	pipeWrapper := builder.OutputPipeWrapper()
	eg, ctx = errgroup.WithContext(ctx)
	eg.Go(func() error {
		b.syncBuildStatus(req.BuildID) <- struct{}{}
65
		close(b.status[req.BuildID].startBuild)
J
jingxiaolu 已提交
66 67 68 69 70 71
		imageID, err = builder.Build()

		// in case there is error during Build stage, the backend will always waiting for content write into
		// the pipeFile, which will cause frontend hangs forever.
		// so if the output type is archive(pipeFile is not empty string) and any error occurred, we write the error
		// message into the pipe to make the goroutine move on instead of hangs.
72
		if err != nil && pipeWrapper != nil {
73 74
			pipeWrapper.Close()
			if perr := ioutil.WriteFile(pipeWrapper.PipeFile, []byte(err.Error()), constant.DefaultRootFileMode); perr != nil {
75
				logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Write error [%v] in to pipe file failed: %v", err, perr)
J
jingxiaolu 已提交
76 77 78 79 80
			}
		}

		return err
	})
81

J
jingxiaolu 已提交
82
	eg.Go(func() error {
83 84 85
		if pipeWrapper == nil {
			return nil
		}
86 87 88 89 90 91
		f, err = exporter.PipeArchiveStream(pipeWrapper)
		defer func() {
			if cErr := f.Close(); cErr != nil {
				logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Closing archive stream pipe %q failed: %v", pipeWrapper.PipeFile, cErr)
			}
		}()
92 93 94 95
		if err != nil {
			return err
		}

96 97 98 99
		reader := bufio.NewReader(f)
		buf := make([]byte, constant.BufferSize, constant.BufferSize)
		for {
			length, err = reader.Read(buf)
100
			if err == io.EOF || pipeWrapper.Done {
101 102
				break
			}
103
			if err != nil {
104 105
				return err
			}
106
			if err = stream.Send(&pb.BuildResponse{
107
				Data: buf[0:length],
108 109 110 111
			}); err != nil {
				return err
			}
		}
112 113
		logrus.WithField(util.LogKeySessionID, req.BuildID).Debugf("Piping build archive stream done")
		return nil
J
jingxiaolu 已提交
114
	})
115

J
jingxiaolu 已提交
116
	go func() {
117
		errC <- eg.Wait()
J
jingxiaolu 已提交
118 119 120
	}()

	select {
121 122
	case err = <-errC:
		close(errC)
J
jingxiaolu 已提交
123 124 125 126 127 128 129 130 131 132 133 134 135
		if err != nil {
			return err
		}
		// export done, send client the imageID
		if err = stream.Send(&pb.BuildResponse{
			Data:    nil,
			ImageID: imageID,
		}); err != nil {
			return err
		}
	case <-stream.Context().Done():
		err = ctx.Err()
		if err != nil && err != context.Canceled {
136
			logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Stream closed with: %v", err)
J
jingxiaolu 已提交
137 138 139 140 141
		}
	}

	return nil
}