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 21 22 23 24 25 26 27 28 29 30 31
	"io/ioutil"

	"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
H
holyfei 已提交
32
func (b *Backend) Build(req *pb.BuildRequest, stream pb.Control_BuildServer) error { // nolint:gocyclo
33 34
	b.wg.Add(1)
	defer b.wg.Done()
J
jingxiaolu 已提交
35 36 37 38 39
	logrus.WithFields(logrus.Fields{
		"BuildType": req.GetBuildType(),
		"BuildID":   req.GetBuildID(),
	}).Info("BuildRequest received")

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

	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 (
55
		imageID string
H
holyfei 已提交
56
		errChan = make(chan error, 1)
J
jingxiaolu 已提交
57 58 59
	)

	pipeWrapper := builder.OutputPipeWrapper()
H
holyfei 已提交
60
	eg, ctx := errgroup.WithContext(ctx)
J
jingxiaolu 已提交
61 62
	eg.Go(func() error {
		b.syncBuildStatus(req.BuildID) <- struct{}{}
63
		b.closeStatusChan(req.BuildID)
H
holyfei 已提交
64 65
		var berr error
		imageID, berr = builder.Build()
J
jingxiaolu 已提交
66 67 68 69 70

		// 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.
H
holyfei 已提交
71
		if berr != nil && pipeWrapper != nil {
72
			pipeWrapper.Close()
H
holyfei 已提交
73 74
			if perr := ioutil.WriteFile(pipeWrapper.PipeFile, []byte(berr.Error()), constant.DefaultRootFileMode); perr != nil {
				logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Write error [%v] in to pipe file failed: %v", berr, perr)
J
jingxiaolu 已提交
75 76 77
			}
		}

H
holyfei 已提交
78
		return berr
J
jingxiaolu 已提交
79
	})
80

J
jingxiaolu 已提交
81
	eg.Go(func() error {
82 83 84
		if pipeWrapper == nil {
			return nil
		}
H
holyfei 已提交
85 86 87 88
		f, perr := exporter.PipeArchiveStream(pipeWrapper)
		if perr != nil {
			return perr
		}
89 90 91 92 93
		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)
			}
		}()
94

95 96 97
		reader := bufio.NewReader(f)
		buf := make([]byte, constant.BufferSize, constant.BufferSize)
		for {
H
holyfei 已提交
98 99
			length, rerr := reader.Read(buf)
			if rerr == io.EOF || pipeWrapper.Done {
100 101
				break
			}
H
holyfei 已提交
102 103
			if rerr != nil {
				return rerr
104
			}
H
holyfei 已提交
105 106 107

			if serr := stream.Send(&pb.BuildResponse{Data: buf[0:length]}); serr != nil {
				return serr
108 109
			}
		}
110 111
		logrus.WithField(util.LogKeySessionID, req.BuildID).Debugf("Piping build archive stream done")
		return nil
J
jingxiaolu 已提交
112
	})
113

J
jingxiaolu 已提交
114
	go func() {
H
holyfei 已提交
115
		errChan <- eg.Wait()
J
jingxiaolu 已提交
116 117 118
	}()

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

	return nil
}