mirror of
https://github.com/alibaba/higress.git
synced 2026-02-23 04:00:51 +08:00
773 lines
21 KiB
Go
773 lines
21 KiB
Go
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package build
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/user"
|
|
"strings"
|
|
|
|
"github.com/alibaba/higress/hgctl/pkg/plugin/option"
|
|
ptypes "github.com/alibaba/higress/hgctl/pkg/plugin/types"
|
|
"github.com/alibaba/higress/hgctl/pkg/plugin/utils"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/mount"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
"github.com/spf13/viper"
|
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
|
)
|
|
|
|
const (
|
|
DefaultBuilderRepository = "higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder"
|
|
DefaultBuilderGo = "1.19"
|
|
DefaultBuilderTinyGo = "0.28.1"
|
|
DefaultBuilderOras = "1.0.0"
|
|
|
|
MediaTypeSpec = "application/vnd.module.wasm.spec.v1+yaml"
|
|
MediaTypeREADME = "application/vnd.module.wasm.doc.v1+markdown"
|
|
MediaTypeREADME_ZH = "application/vnd.module.wasm.doc.v1.zh+markdown"
|
|
MediaTypeREADME_EN = "application/vnd.module.wasm.doc.v1.en+markdown"
|
|
MediaTypeIcon = "application/vnd.module.wasm.icon.v1+png"
|
|
MediaTypePlugin = "application/vnd.oci.image.layer.v1.tar+gzip"
|
|
|
|
HostTempDirPattern = "higress-wasm-go-build-*"
|
|
HostDockerEntryPattern = "higress-wasm-go-build-docker-entrypoint-*.sh"
|
|
|
|
ContainerWorkDir = "/workspace"
|
|
ContainerTempDir = "/higress_temp" // the directory to temporarily store the build products
|
|
ContainerOutDir = "/output"
|
|
ContainerDockerAuth = "/root/.docker/config.json"
|
|
ContainerEntryFile = "docker-entrypoint.sh"
|
|
ContainerEntryFilePath = "/" + ContainerEntryFile
|
|
)
|
|
|
|
type Builder struct {
|
|
OptionFile string
|
|
option.BuildOptions
|
|
Username, Password string
|
|
|
|
repository string
|
|
tempDir string
|
|
dockerEntrypoint string
|
|
uid, gid string
|
|
manualClean bool
|
|
|
|
containerID string
|
|
containerConf types.ContainerCreateConfig
|
|
dockerCli *client.Client
|
|
w io.Writer
|
|
sig chan os.Signal // watch interrupt
|
|
stop chan struct{} // stop the build process when an interruption occurs
|
|
done chan struct{} // signal that the build process is finished
|
|
|
|
utils.Debugger
|
|
*utils.YesOrNoPrinter
|
|
}
|
|
|
|
func NewBuilder(f ConfigFunc) (*Builder, error) {
|
|
b := new(Builder)
|
|
if err := b.config(f); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func NewCommand() *cobra.Command {
|
|
var bld Builder
|
|
v := viper.New()
|
|
|
|
buildCmd := &cobra.Command{
|
|
Use: "build",
|
|
Aliases: []string{"bld", "b"},
|
|
Short: "Build Golang WASM plugin",
|
|
Example: ` # If the option.yaml file exists in the current path, do the following:
|
|
hgctl plugin build
|
|
|
|
# Using "--model(-s)" to specify the WASM plugin configuration structure name, e.g. "HelloWorldConfig"
|
|
hgctl plugin build --model HelloWorldConfig
|
|
|
|
# Using "--output-type(-t)" and "--output-dest(-d)" to push the build products as an OCI image to the specified repository
|
|
docker login
|
|
hgctl plugin build -s BasicAuthConfig -t image -d docker.io/<your_username>/<your_image>
|
|
`,
|
|
PreRun: func(cmd *cobra.Command, args []string) {
|
|
cmdutil.CheckErr(bld.config(func(b *Builder) error {
|
|
return b.parseOptions(v, cmd)
|
|
}))
|
|
},
|
|
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
cmdutil.CheckErr(bld.Build())
|
|
},
|
|
}
|
|
|
|
bld.bindFlags(v, buildCmd.PersistentFlags())
|
|
|
|
return buildCmd
|
|
}
|
|
|
|
func (b *Builder) bindFlags(v *viper.Viper, flags *pflag.FlagSet) {
|
|
option.AddOptionFileFlag(&b.OptionFile, flags)
|
|
flags.StringVarP(&b.Username, "username", "u", "", "Username for pushing image to the docker repository")
|
|
flags.StringVarP(&b.Password, "password", "p", "", "Password for pushing image to the docker repository")
|
|
v.BindPFlags(flags)
|
|
|
|
// this binding ensures that flags explicitly set on the command line have the
|
|
// highest priority, and if they are not set, they are read from the configuration file.
|
|
flags.StringP("builder-go", "g", DefaultBuilderGo, "Golang version in the official builder image")
|
|
v.BindPFlag("build.builder.go", flags.Lookup("builder-go"))
|
|
v.SetDefault("build.builder.go", DefaultBuilderGo)
|
|
|
|
flags.StringP("builder-tinygo", "n", DefaultBuilderTinyGo, "TinyGo version in the official builder image")
|
|
v.BindPFlag("build.builder.tinygo", flags.Lookup("builder-tinygo"))
|
|
v.SetDefault("build.builder.tinygo", DefaultBuilderTinyGo)
|
|
|
|
flags.StringP("builder-oras", "r", DefaultBuilderOras, "ORAS version in official the builder image")
|
|
v.BindPFlag("build.builder.oras", flags.Lookup("builder-oras"))
|
|
v.SetDefault("build.builder.oras", DefaultBuilderOras)
|
|
|
|
flags.StringP("input", "i", "./", "Directory of the WASM plugin project to be built")
|
|
v.BindPFlag("build.input", flags.Lookup("input"))
|
|
v.SetDefault("build.input", "./")
|
|
|
|
flags.StringP("output-type", "t", "files", "Output type of the build products. [files, image]")
|
|
v.BindPFlag("build.output.type", flags.Lookup("output-type"))
|
|
v.SetDefault("build.output.type", "files")
|
|
|
|
flags.StringP("output-dest", "d", "./out", "Output destination of the build products")
|
|
v.BindPFlag("build.output.dest", flags.Lookup("output-dest"))
|
|
v.SetDefault("build.output.dest", "./out")
|
|
|
|
flags.StringP("docker-auth", "a", "~/.docker/config.json", "Authentication configuration for pushing image to the docker repository")
|
|
v.BindPFlag("build.docker-auth", flags.Lookup("docker-auth"))
|
|
v.SetDefault("build.docker-auth", "~/.docker/config.json")
|
|
|
|
flags.StringP("model-dir", "m", "./", "Directory of the WASM plugin configuration structure")
|
|
v.BindPFlag("build.model-dir", flags.Lookup("model-dir"))
|
|
v.SetDefault("build.model-dir", "./")
|
|
|
|
flags.StringP("model", "s", "", "Structure name of the WASM plugin configuration")
|
|
v.BindPFlag("build.model", flags.Lookup("model"))
|
|
v.SetDefault("build.model", "PluginConfig")
|
|
|
|
flags.BoolP("debug", "", false, "Enable debug mode")
|
|
v.BindPFlag("build.debug", flags.Lookup("debug"))
|
|
v.SetDefault("build.debug", false)
|
|
}
|
|
|
|
func (b *Builder) Build() (err error) {
|
|
b.Debugf("build options: \n%s\n", b.String())
|
|
|
|
go func() {
|
|
err = b.doBuild()
|
|
}()
|
|
|
|
// wait for an interruption to occur or finishing the build
|
|
select {
|
|
case <-b.sig:
|
|
b.interrupt()
|
|
b.Nof("\nInterrupt ...\n")
|
|
// wait for the doBuild process to exit, otherwise there will be unexpected bugs
|
|
b.waitForFinished()
|
|
// if the build process is interrupted, then we ignore the flag `manualClean` and clean up
|
|
// TODO(WeixinX): How do we clean up uploaded image when an interruption occurs?
|
|
b.Debugln("clean up for interrupting ...")
|
|
b.CleanupForError()
|
|
os.Exit(0)
|
|
|
|
case <-b.done:
|
|
if err != nil {
|
|
if !b.manualClean {
|
|
b.Debugln("clean up for error ...")
|
|
b.CleanupForError()
|
|
}
|
|
return
|
|
}
|
|
if !b.manualClean {
|
|
b.Debugln("clean up for normal ...")
|
|
b.Cleanup()
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
var (
|
|
waitIcon = "[-]"
|
|
successfulIcon = "[√]"
|
|
)
|
|
|
|
func (b *Builder) doBuild() (err error) {
|
|
// finish here does not mean that the build was successful,
|
|
// but that the doBuild process is complete
|
|
defer b.finish()
|
|
|
|
if err = b.generateMetadata(); err != nil {
|
|
return errors.Wrap(err, "failed to generate wasm plugin metadata files")
|
|
}
|
|
|
|
b.Printf("%s pull the builder image ...\n", waitIcon)
|
|
ctx := context.TODO()
|
|
if err = b.imagePull(ctx); err != nil {
|
|
return errors.Wrapf(err, "failed to pull the builder image %s", b.builderImageRef())
|
|
}
|
|
b.Yesf("%s pull the builder image: %s\n", successfulIcon, b.builderImageRef())
|
|
|
|
if err = b.addContainerConfByOutType(); err != nil {
|
|
return errors.Wrapf(err, "failed to add the additional container configuration for output type %q", b.Output.Type)
|
|
}
|
|
|
|
b.Printf("%s create the builder container ...\n", waitIcon)
|
|
if err = b.containerCreate(ctx); err != nil {
|
|
return errors.Wrap(err, "failed to create the builder container")
|
|
}
|
|
b.Yesf("%s create the builder container: %s\n", successfulIcon, b.containerID)
|
|
|
|
b.Printf("%s start the builder container ...\n", waitIcon)
|
|
if err = b.containerStart(ctx); err != nil {
|
|
return errors.Wrap(err, "failed to start the builder container")
|
|
}
|
|
|
|
if b.Output.Type == "files" {
|
|
b.Yesf("%s finish building!\n", successfulIcon)
|
|
} else if b.Output.Type == "image" {
|
|
b.Yesf("%s finish building and pushing!\n", successfulIcon)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var errBuildAbort = errors.New("build aborted")
|
|
|
|
func (b *Builder) generateMetadata() error {
|
|
// spec.yaml
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
spec, err := os.Create(b.SpecYAMLPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer spec.Close()
|
|
meta, err := ptypes.ParseGoSrc(b.ModelDir, b.Model)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = utils.MarshalYamlWithIndentTo(spec, meta, 2); err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO(WeixinX): More languages need to be supported
|
|
// README.md is required, README_{lang}.md is optional
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
usages, err := meta.GetUsages()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get wasm usage")
|
|
}
|
|
for i, u := range usages {
|
|
// since `usages` are ordered by `I18nType` and currently only `en-US` and
|
|
// `zh-CN` are available, en-US is the default README.md language when en-US is
|
|
// present (because after sorting it is in the first place)
|
|
suffix := true
|
|
if i == 0 {
|
|
suffix = false
|
|
}
|
|
if err = genMarkdownUsage(&u, b.tempDir, suffix); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) imagePull(ctx context.Context) error {
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
r, err := b.dockerCli.ImagePull(ctx, b.builderImageRef(), types.ImagePullOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
io.Copy(b.w, r)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) addContainerConfByOutType() error {
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
|
|
var err error
|
|
switch b.Output.Type {
|
|
case "files":
|
|
err = b.filesHandler()
|
|
case "image":
|
|
err = b.imageHandler()
|
|
default:
|
|
return errors.New("invalid output option, output type is unknown")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) containerCreate(ctx context.Context) error {
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
|
|
resp, err := b.dockerCli.ContainerCreate(ctx, b.containerConf.Config, b.containerConf.HostConfig,
|
|
b.containerConf.NetworkingConfig, b.containerConf.Platform, b.containerConf.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.containerID = resp.ID
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) containerStart(ctx context.Context) error {
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
if err := b.dockerCli.ContainerStart(ctx, b.containerID, types.ContainerStartOptions{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
statusCh, errCh := b.dockerCli.ContainerWait(ctx, b.containerID, container.WaitConditionNotRunning)
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case <-statusCh:
|
|
}
|
|
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
logs, err := b.dockerCli.ContainerLogs(ctx, b.containerID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if b.isInterrupted() {
|
|
return errBuildAbort
|
|
}
|
|
_, err = stdcopy.StdCopy(b.w, b.w, logs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var errWriteDockerEntrypoint = errors.New("failed to write docker entrypoint")
|
|
|
|
func (b *Builder) filesHandler() error {
|
|
b.containerConf.HostConfig.Mounts = append(b.containerConf.HostConfig.Mounts, mount.Mount{
|
|
// output dir for the build products
|
|
Type: mount.TypeBind,
|
|
Source: b.Output.Dest,
|
|
Target: ContainerOutDir,
|
|
})
|
|
|
|
ft := &FilesTmplFields{
|
|
BuildSrcDir: ContainerWorkDir,
|
|
BuildDestDir: ContainerTempDir,
|
|
Output: ContainerOutDir,
|
|
UID: b.uid,
|
|
GID: b.uid,
|
|
Debug: b.Debug,
|
|
}
|
|
|
|
if err := genFilesDockerEntrypoint(ft, b.dockerEntrypoint); err != nil {
|
|
return errors.Wrap(err, errWriteDockerEntrypoint.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
optionalProducts = [][2]string{
|
|
{"README_ZH.md", MediaTypeREADME_ZH},
|
|
{"README_EN.md", MediaTypeREADME_EN},
|
|
{"icon.png", MediaTypeIcon},
|
|
}
|
|
)
|
|
|
|
// TODO(WeixinX): If the image exists, no push is performed
|
|
func (b *Builder) imageHandler() error {
|
|
products := ""
|
|
for i, p := range optionalProducts {
|
|
fileName := p[0]
|
|
mediaType := p[1]
|
|
if i == 0 {
|
|
products = fmt.Sprintf("%s %s", fileName, mediaType)
|
|
} else {
|
|
products = fmt.Sprintf("%s %s %s", products, fileName, mediaType)
|
|
}
|
|
}
|
|
|
|
// spec.yaml, README.md and plugin.tar.gz are required
|
|
basicCmd := fmt.Sprintf("oras push %s -u %s -p %s ./spec.yaml:%s ./README.md:%s",
|
|
b.Output.Dest, b.Username, b.Password, MediaTypeSpec, MediaTypeREADME)
|
|
|
|
if b.Username == "" || b.Password == "" {
|
|
basicCmd = fmt.Sprintf("oras push %s ./spec.yaml:%s ./README.md:%s",
|
|
b.Output.Dest, MediaTypeSpec, MediaTypeREADME)
|
|
|
|
b.containerConf.HostConfig.Mounts = append(b.containerConf.HostConfig.Mounts, mount.Mount{
|
|
// docker auth
|
|
Type: mount.TypeBind,
|
|
Source: b.DockerAuth,
|
|
Target: ContainerDockerAuth,
|
|
})
|
|
}
|
|
|
|
it := &ImageTmplFields{
|
|
BuildSrcDir: ContainerWorkDir,
|
|
BuildDestDir: ContainerTempDir,
|
|
Output: ContainerOutDir,
|
|
Username: b.Username,
|
|
Password: b.Password,
|
|
BasicCmd: basicCmd,
|
|
Products: products,
|
|
MediaTypePlugin: MediaTypePlugin,
|
|
Debug: b.Debug,
|
|
}
|
|
|
|
if err := genImageDockerEntrypoint(it, b.dockerEntrypoint); err != nil {
|
|
return errors.Wrap(err, errWriteDockerEntrypoint.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConfigFunc is customized to set the fields of Builder
|
|
type ConfigFunc func(b *Builder) error
|
|
|
|
func (b *Builder) config(f ConfigFunc) (err error) {
|
|
if err = f(b); err != nil {
|
|
return err
|
|
}
|
|
|
|
// builder-go
|
|
b.Builder.Go = strings.TrimSpace(b.Builder.Go)
|
|
if b.Builder.Go == "" {
|
|
b.Builder.Go = DefaultBuilderGo
|
|
}
|
|
|
|
// builder-tinygo
|
|
b.Builder.TinyGo = strings.TrimSpace(b.Builder.TinyGo)
|
|
if b.Builder.TinyGo == "" {
|
|
b.Builder.TinyGo = DefaultBuilderTinyGo
|
|
}
|
|
|
|
// builder-oras
|
|
b.Builder.Oras = strings.TrimSpace(b.Builder.Oras)
|
|
if b.Builder.Oras == "" {
|
|
b.Builder.Oras = DefaultBuilderOras
|
|
}
|
|
|
|
// input
|
|
b.Input = strings.TrimSpace(b.Input)
|
|
if b.Input == "" {
|
|
b.Input = "./"
|
|
}
|
|
inp, err := utils.GetAbsolutePath(b.Input)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to parse input option %q", b.Input)
|
|
}
|
|
b.Input = inp
|
|
|
|
// output-type
|
|
b.Output.Type = strings.ToLower(strings.TrimSpace(b.Output.Type))
|
|
if b.Output.Type == "" {
|
|
b.Output.Type = "files"
|
|
}
|
|
if b.Output.Type != "files" && b.Output.Type != "image" {
|
|
return errors.Errorf("invalid output type: %q, must be `files` or `image`", b.Output.Type)
|
|
}
|
|
|
|
// output-dest
|
|
b.Output.Dest = strings.TrimSpace(b.Output.Dest)
|
|
if b.Output.Dest == "" {
|
|
b.Output.Dest = "./out"
|
|
}
|
|
out := b.Output.Dest
|
|
if b.Output.Type == "files" {
|
|
out, err = utils.GetAbsolutePath(b.Output.Dest)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to parse output destination %q", b.Output.Dest)
|
|
}
|
|
err = os.MkdirAll(b.Output.Dest, 0755)
|
|
if err != nil && !os.IsExist(err) {
|
|
return errors.Wrapf(err, "failed to create output destination %q", b.Output.Dest)
|
|
}
|
|
}
|
|
b.Output.Dest = out
|
|
|
|
// docker-auth
|
|
b.DockerAuth = strings.TrimSpace(b.DockerAuth)
|
|
if b.DockerAuth == "" {
|
|
b.DockerAuth = "~/.docker/config.json"
|
|
}
|
|
auth, err := utils.GetAbsolutePath(b.DockerAuth)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to parse docker authentication %q", b.DockerAuth)
|
|
}
|
|
b.DockerAuth = auth
|
|
|
|
// model-dir
|
|
b.ModelDir = strings.TrimSpace(b.ModelDir)
|
|
if b.ModelDir == "" {
|
|
b.ModelDir = "./"
|
|
}
|
|
|
|
// option-file/username/password/model/debug: nothing to deal with
|
|
|
|
// the unexported fields that users do not need to care about are as follows:
|
|
b.repository = DefaultBuilderRepository
|
|
|
|
b.tempDir, err = os.MkdirTemp("", HostTempDirPattern)
|
|
if err != nil && !os.IsExist(err) {
|
|
return errors.Wrap(err, "failed to create the host temporary dir")
|
|
}
|
|
|
|
dockerEp, err := os.CreateTemp("", HostDockerEntryPattern)
|
|
if err != nil && !os.IsExist(err) {
|
|
return errors.Wrap(err, "failed to create the docker entrypoint file")
|
|
}
|
|
err = dockerEp.Chmod(0777)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.dockerEntrypoint = dockerEp.Name()
|
|
dockerEp.Close()
|
|
|
|
u, err := user.Current()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get the current user information")
|
|
}
|
|
b.uid, b.gid = u.Uid, u.Gid
|
|
|
|
b.containerConf = types.ContainerCreateConfig{
|
|
Name: "higress-wasm-go-builder",
|
|
Config: &container.Config{
|
|
Image: b.builderImageRef(),
|
|
Env: []string{
|
|
"GO111MODULE=on",
|
|
"GOPROXY=https://goproxy.cn,direct",
|
|
},
|
|
WorkingDir: ContainerWorkDir,
|
|
Entrypoint: []string{ContainerEntryFilePath},
|
|
},
|
|
HostConfig: &container.HostConfig{
|
|
NetworkMode: "host",
|
|
Mounts: []mount.Mount{
|
|
{ // input dir that includes the wasm plugin source: main.go ...
|
|
Type: mount.TypeBind,
|
|
Source: b.Input,
|
|
Target: ContainerWorkDir,
|
|
},
|
|
{ // temp dir that includes the wasm plugin metadata: spec.yaml and README.md ...
|
|
Type: mount.TypeBind,
|
|
Source: b.tempDir,
|
|
Target: ContainerTempDir,
|
|
},
|
|
{ // entrypoint
|
|
Type: mount.TypeBind,
|
|
Source: b.dockerEntrypoint,
|
|
Target: ContainerEntryFilePath,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if b.dockerCli == nil {
|
|
b.dockerCli, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to initialize the docker client")
|
|
}
|
|
}
|
|
|
|
if b.w == nil {
|
|
b.w = os.Stdout
|
|
}
|
|
|
|
signalNotify(b)
|
|
|
|
if b.Debugger == nil {
|
|
b.Debugger = utils.NewDefaultDebugger(b.Debug, b.w)
|
|
}
|
|
|
|
if b.YesOrNoPrinter == nil {
|
|
b.YesOrNoPrinter = utils.NewPrinter(b.w, utils.DefaultIdent, utils.DefaultYes, utils.DefaultNo)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) parseOptions(v *viper.Viper, cmd *cobra.Command) error {
|
|
allOpt, err := option.ParseOptions(b.OptionFile, v, cmd.PersistentFlags())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.BuildOptions = allOpt.Build
|
|
|
|
b.w = cmd.OutOrStdout()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Builder) finish() {
|
|
select {
|
|
case <-b.done:
|
|
default:
|
|
close(b.done)
|
|
}
|
|
}
|
|
|
|
func (b *Builder) waitForFinished() {
|
|
<-b.done
|
|
}
|
|
|
|
func (b *Builder) interrupt() {
|
|
select {
|
|
case <-b.stop:
|
|
default:
|
|
close(b.stop)
|
|
}
|
|
}
|
|
|
|
func (b *Builder) isInterrupted() bool {
|
|
if b.stop == nil {
|
|
return true
|
|
}
|
|
select {
|
|
case <-b.stop:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// WithManualClean if set this option, then the temporary files and the container
|
|
// will not be cleaned up automatically, and you need to clean up manually
|
|
func (b *Builder) WithManualClean() {
|
|
b.manualClean = true
|
|
}
|
|
|
|
func (b *Builder) WithWriter(w io.Writer) {
|
|
b.w = w
|
|
}
|
|
|
|
// CleanupForError cleans up the temporary files and the container when an error occurs
|
|
func (b *Builder) CleanupForError() {
|
|
b.Cleanup()
|
|
b.removeOutputDest()
|
|
}
|
|
|
|
// Cleanup cleans up the temporary files and the container
|
|
func (b *Builder) Cleanup() {
|
|
b.removeTempDir()
|
|
b.removeDockerEntrypoint()
|
|
b.removeBuilderContainer()
|
|
b.closeDockerCli()
|
|
}
|
|
|
|
func (b *Builder) removeOutputDest() {
|
|
if b.BuildOptions.Output.Type == "files" {
|
|
b.Debugf("remove output destination %q\n", b.BuildOptions.Output.Dest)
|
|
os.RemoveAll(b.BuildOptions.Output.Dest)
|
|
}
|
|
}
|
|
|
|
func (b *Builder) removeTempDir() {
|
|
if b.tempDir != "" {
|
|
b.Debugf("remove temporary directory %q\n", b.tempDir)
|
|
os.RemoveAll(b.tempDir)
|
|
}
|
|
}
|
|
|
|
func (b *Builder) removeDockerEntrypoint() {
|
|
if b.dockerEntrypoint != "" {
|
|
b.Debugf("delete docker entrypoint %q\n", b.dockerEntrypoint)
|
|
os.Remove(b.dockerEntrypoint)
|
|
}
|
|
}
|
|
|
|
func (b *Builder) removeBuilderContainer() {
|
|
if b.containerID != "" {
|
|
err := b.dockerCli.ContainerRemove(context.TODO(), b.containerID, types.ContainerRemoveOptions{Force: true})
|
|
if err != nil {
|
|
b.Debugf("failed to remove container (%s): %s\n", b.containerConf.Name, b.containerID)
|
|
} else {
|
|
b.Debugf("remove container (%s): %s\n", b.containerConf.Name, b.containerID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Builder) closeDockerCli() {
|
|
if b.dockerCli != nil {
|
|
b.Debugln("close the docker client")
|
|
b.dockerCli.Close()
|
|
}
|
|
}
|
|
|
|
func (b *Builder) builderImageRef() string {
|
|
return fmt.Sprintf("%s:go%s-tinygo%s-oras%s", b.repository, b.Builder.Go, b.Builder.TinyGo, b.Builder.Oras)
|
|
}
|
|
|
|
func (b *Builder) SpecYAMLPath() string {
|
|
return fmt.Sprintf("%s/spec.yaml", b.tempDir)
|
|
}
|
|
|
|
func (b *Builder) TempDir() string {
|
|
return b.tempDir
|
|
}
|
|
|
|
func (b *Builder) String() string {
|
|
by, err := json.MarshalIndent(b, "", " ")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(by)
|
|
}
|