// 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 ( "bufio" "context" "io" "io/ioutil" "os" "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") ctx := context.WithValue(stream.Context(), util.LogFieldKey(util.LogKeySessionID), req.BuildID) 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 ( f *os.File length int imageID string eg *errgroup.Group errC = make(chan error, 1) ) pipeWrapper := builder.OutputPipeWrapper() eg, ctx = errgroup.WithContext(ctx) eg.Go(func() error { b.syncBuildStatus(req.BuildID) <- struct{}{} close(b.status[req.BuildID].startBuild) 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. if err != nil && pipeWrapper != nil { pipeWrapper.Close() if perr := ioutil.WriteFile(pipeWrapper.PipeFile, []byte(err.Error()), constant.DefaultRootFileMode); perr != nil { logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Write error [%v] in to pipe file failed: %v", err, perr) } } return err }) eg.Go(func() error { if pipeWrapper == nil { return nil } 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) } }() if err != nil { return err } reader := bufio.NewReader(f) buf := make([]byte, constant.BufferSize, constant.BufferSize) for { length, err = reader.Read(buf) if err == io.EOF || pipeWrapper.Done { break } if err != nil { return err } if err = stream.Send(&pb.BuildResponse{ Data: buf[0:length], }); err != nil { return err } } logrus.WithField(util.LogKeySessionID, req.BuildID).Debugf("Piping build archive stream done") return nil }) go func() { errC <- eg.Wait() }() select { case err = <-errC: close(errC) 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 { logrus.WithField(util.LogKeySessionID, req.BuildID).Warnf("Stream closed with: %v", err) } } return nil }