Skip to content

Commit

Permalink
feat: dockerfile action support build context from user input
Browse files Browse the repository at this point in the history
Signed-off-by: Ash <[email protected]>
  • Loading branch information
iutx committed Sep 12, 2024
1 parent 088ce0b commit 23ac102
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 112 deletions.
13 changes: 8 additions & 5 deletions actions/dockerfile/1.0/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
FROM registry.erda.cloud/erda-actions/custom-script:2.0 AS builder
FROM --platform=$TARGETPLATFORM registry.erda.cloud/retag/buildkit:v0.11.3 as buildkit
FROM --platform=$TARGETPLATFORM registry.erda.cloud/erda-x/golang:1.22 AS builder

ENV CGO_ENABLED 0

COPY . /go/src/github.com/erda-project/erda-actions
WORKDIR /go/src/github.com/erda-project/erda-actions

ENV CGO_ENABLED=0

COPY . .

RUN go build -o /opt/action/run github.com/erda-project/erda-actions/actions/dockerfile/1.0/internal/cmd

FROM registry.erda.cloud/erda-actions/custom-script:2.0
FROM --platform=$TARGETPLATFORM registry.erda.cloud/erda-x/debian-bookworm:12

COPY --from=builder /opt/action/run /opt/action/run
COPY --from=buildkit /usr/bin/buildctl /usr/bin/buildctl
RUN chmod +x /opt/action/run
91 changes: 91 additions & 0 deletions actions/dockerfile/1.0/internal/pkg/build/buildkit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package build

import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"

"github.com/erda-project/erda-actions/actions/dockerfile/1.0/internal/pkg/conf"
pkgconf "github.com/erda-project/erda-actions/pkg/envconf"
"github.com/erda-project/erda-infra/pkg/strutil"
)

const (
defaultCaCertPath = "/.buildkit/ca.pem"
defaultCertPath = "/.buildkit/cert.pem"
defaultCertKeyPath = "/.buildkit/key.pem"
)

type buildkit struct {
config *conf.Conf
args []string
}

func NewBuildkit(c *conf.Conf) Builder {
args := []string{
"--addr", c.BuildkitdAddr,
fmt.Sprintf("--tlscacert=%s", defaultCaCertPath),
fmt.Sprintf("--tlscert=%s", defaultCertPath),
fmt.Sprintf("--tlskey=%s", defaultCertKeyPath),
"build",
"--frontend", "dockerfile.v0",
"--local", "context=" + c.Context,
"--local", "dockerfile=" + resolveDockerfileDir(c),
"--opt", fmt.Sprintf("platform=%s", pkgconf.GetTargetPlatforms()),
}

return &buildkit{
config: c,
args: args,
}
}

func (b *buildkit) Build(p *Params, o *OutPut) error {
// generate command from action params provided
b.appendBuildArgs(p.Args)
b.appendBuildContexts(p.BuildContext)

// set output
b.args = append(b.args,
"--output",
fmt.Sprintf("type=image,name=%s,push=%s", o.Image, strconv.FormatBool(o.Push)),
)

buildCmd := exec.Command("buildctl", b.args...)
fmt.Println(strutil.Join(buildCmd.Args, " ", false))

buildCmd.Dir = b.config.WorkDir
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr

if err := buildCmd.Run(); err != nil {
return fmt.Errorf("failed to build image: %v", err)
}

fmt.Fprintf(os.Stdout, "Successfully built and pushed image: %s\n", o.Image)
return nil
}

func resolveDockerfileDir(c *conf.Conf) string {
if path.IsAbs(c.Path) {
return filepath.Dir(c.Path)
}
return filepath.Dir(path.Join(c.Context, c.Path))
}

func (b *buildkit) appendBuildArgs(args map[string]string) {
for k, v := range args {
b.args = append(b.args, "--opt", fmt.Sprintf("build-arg:%s=%s", k, v))
}
}

func (b *buildkit) appendBuildContexts(buildContexts map[string]string) {
for k, v := range buildContexts {
b.args = append(b.args,
"--local", fmt.Sprintf("%s=%s", k, v),
"--opt", fmt.Sprintf("context:%s=local:%s", k, k))
}
}
77 changes: 77 additions & 0 deletions actions/dockerfile/1.0/internal/pkg/build/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package build

import (
"fmt"
"os"
"os/exec"
"strconv"

"github.com/erda-project/erda-actions/actions/dockerfile/1.0/internal/pkg/conf"
"github.com/erda-project/erda-actions/pkg/docker"
"github.com/erda-project/erda/apistructs"
)

type dockerBuilder struct {
config *conf.Conf
args []string
}

func NewDocker(c *conf.Conf) Builder {
args := []string{
"build",
".", // set current dir is build context
"-f", c.Path,
}

return &dockerBuilder{
config: c,
args: args,
}
}

func (d *dockerBuilder) Build(p *Params, o *OutPut) error {
// generate command from action params provided
d.appendBuildArgs(p.Args)
d.appendBuildContexts(p.BuildContext)

// set resource limit
d.args = append(d.args,
"--cpu-quota", strconv.FormatFloat(d.config.CPU*100000, 'f', 0, 64),
"--memory", strconv.FormatInt(int64(d.config.Memory*apistructs.MB), 10),
)

// generate build command
buildCmd := exec.Command("docker", d.args...)
fmt.Fprintf(os.Stdout, "Docker build command: %v\n", buildCmd.Args)

buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
buildCmd.Dir = d.config.WorkDir

if err := buildCmd.Run(); err != nil {
return fmt.Errorf("failed to build docker image: %v", err)
}

if o.Push {
if err := docker.PushByCmd(o.Image, ""); err != nil {
return fmt.Errorf("failed to push docker image: %v", err)
}
}

fmt.Fprintf(os.Stdout, "Successfully built and pushed image: %s\n", o.Image)
return nil
}

// appendBuildArgs
func (d *dockerBuilder) appendBuildArgs(args map[string]string) {
for k, v := range args {
d.args = append(d.args, "--build-arg", fmt.Sprintf("%s=%s", k, v))
}
}

// appendBuildContexts
func (d *dockerBuilder) appendBuildContexts(contexts map[string]string) {
for k, v := range contexts {
d.args = append(d.args, "--build-context", fmt.Sprintf("%s=%s", k, v))
}
}
143 changes: 46 additions & 97 deletions actions/dockerfile/1.0/internal/pkg/build/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,16 @@ package build
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"

pkgconf "github.com/erda-project/erda-actions/pkg/envconf"
"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/pkg/envconf"
"github.com/erda-project/erda/pkg/filehelper"
"github.com/erda-project/erda/pkg/metadata"
"github.com/erda-project/erda/pkg/strutil"
"github.com/labstack/gommon/random"
"github.com/pkg/errors"

Expand All @@ -27,6 +22,20 @@ import (
"github.com/erda-project/erda-actions/pkg/pack"
)

type Builder interface {
Build(p *Params, o *OutPut) error
}

type OutPut struct {
Image string
Push bool
}

type Params struct {
Args map[string]string
BuildContext map[string]string
}

// Execute 自定义 dockerfile 构建应用镜像
func Execute() error {
var cfg conf.Conf
Expand Down Expand Up @@ -72,12 +81,12 @@ func packAndPushImage(cfg conf.Conf) error {
return err
}

originalDockerfileContent, err := ioutil.ReadFile(cfg.Path)
originalDockerfileContent, err := os.ReadFile(cfg.Path)
if err != nil {
return err
}
newDockerfileContent := dockerfile.ReplaceOrInsertBuildArgToDockerfile(originalDockerfileContent, cfg.BuildArgs)
if err = ioutil.WriteFile(cfg.Path, newDockerfileContent, 0644); err != nil {
if err = os.WriteFile(cfg.Path, newDockerfileContent, 0644); err != nil {
return err
}
}
Expand All @@ -91,16 +100,37 @@ func packAndPushImage(cfg conf.Conf) error {
"DICE_WORKSPACE": cfg.DiceWorkspace,
}

if cfg.BuildkitEnable == "true" {
if err := packWithBuildkit(cfg, repo, buildArgs); err != nil {
fmt.Fprintf(os.Stdout, "failed to pack with buildkit: %v\n", err)
return err
}
buildContext := cfg.BuildContext
if buildContext == nil {
buildContext = make(map[string]string)
}

var builder Builder

isBuildkitEnable, err := strconv.ParseBool(cfg.BuildkitEnable)
if err != nil {
fmt.Fprintf(os.Stdout, "unparsed builder keyword: %v\n", err)
return err
}

if isBuildkitEnable {
builder = NewBuildkit(&cfg)
} else {
if err := packWithDocker(cfg, repo, buildArgs); err != nil {
fmt.Fprintf(os.Stdout, "failed to pack with docker: %v\n", err)
return err
}
builder = NewDocker(&cfg)
}

if err := builder.Build(
&Params{
Args: buildArgs,
BuildContext: buildContext,
},
&OutPut{
Image: repo,
Push: true, // push default now.
},
); err != nil {
fmt.Fprintf(os.Stdout, "failed to build: %v\n", err)
return err
}

// upload metadata
Expand All @@ -125,87 +155,6 @@ func packAndPushImage(cfg conf.Conf) error {
return nil
}

func packWithDocker(cfg conf.Conf, repo string, args map[string]string) error {
argsSlice := make([]string, 0)

for k, v := range args {
argsSlice = append(argsSlice, "--build-arg", fmt.Sprintf("%s=%s", k, v))
}

buildCmdArgs := []string{"build"}
buildCmdArgs = append(buildCmdArgs, argsSlice...)
buildCmdArgs = append(buildCmdArgs,
"--cpu-quota", strconv.FormatFloat(float64(cfg.CPU*100000), 'f', 0, 64),
"--memory", strconv.FormatInt(int64(cfg.Memory*apistructs.MB), 10),
"-t", repo,
"-f", cfg.Path, ".")

packCmd := exec.Command("docker", buildCmdArgs...)

fmt.Fprintf(os.Stdout, "packCmd: %v\n", packCmd.Args)
packCmd.Stdout = os.Stdout
packCmd.Stderr = os.Stderr
if err := packCmd.Run(); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "successfully build app image: %s\n", repo)

// docker push 业务镜像至集群 registry
if err := docker.PushByCmd(repo, ""); err != nil {
return err
}

return nil
}

func packWithBuildkit(cfg conf.Conf, repo string, args map[string]string) error {
argsSlice := make([]string, 0)

for k, v := range args {
argsSlice = append(argsSlice, "--opt", fmt.Sprintf("build-arg:%s=%s", k, v))
}

buildCmdArgs := []string{
"--addr", cfg.BuildkitdAddr,
"--tlscacert=/.buildkit/ca.pem",
"--tlscert=/.buildkit/cert.pem",
"--tlskey=/.buildkit/key.pem",
"build",
"--frontend", "dockerfile.v0",
}

// append args, e.g. --opt k=v
buildCmdArgs = append(buildCmdArgs, argsSlice...)

// Get dockerfile dir
var dfDir string
if path.IsAbs(cfg.Path) {
dfDir = filepath.Dir(cfg.Path)
} else {
dfDir = filepath.Dir(path.Join(cfg.Context, cfg.Path))
}

// append build source and output param.
buildCmdArgs = append(buildCmdArgs,
"--local", "context="+cfg.Context,
"--local", "dockerfile="+dfDir,
"--opt", fmt.Sprintf("platform=%s", pkgconf.GetTargetPlatforms()),
"--output", "type=image,name="+repo+",push=true",
)

buildkitCmd := exec.Command("buildctl", buildCmdArgs...)
fmt.Println(strutil.Join(buildkitCmd.Args, " ", false))

buildkitCmd.Dir = cfg.WorkDir
buildkitCmd.Stdout = os.Stdout
buildkitCmd.Stderr = os.Stderr
if err := buildkitCmd.Run(); err != nil {
return err
}

return nil
}

// 生成业务镜像名称
func getRepo(cfg conf.Conf) string {
// registry url
Expand Down
7 changes: 4 additions & 3 deletions actions/dockerfile/1.0/internal/pkg/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ type Conf struct {
Path string `env:"ACTION_PATH" required:"true"`
BuildArgsStr string `env:"ACTION_BUILD_ARGS"` // 用于渲染 dockerfile
BuildArgs map[string]string
Service string `env:"ACTION_SERVICE"` // TODO deprecated
Image *DockerImage `env:"ACTION_IMAGE"`
Registry *DockerRegistry `env:"ACTION_REGISTRY"`
BuildContext map[string]string `env:"ACTION_BUILD_CONTEXT"`
Service string `env:"ACTION_SERVICE"` // TODO deprecated
Image *DockerImage `env:"ACTION_IMAGE"`
Registry *DockerRegistry `env:"ACTION_REGISTRY"`
// pipeline 注入,镜像生成时使用
TaskName string `env:"PIPELINE_TASK_NAME" default:"unknown"`
ProjectAppAbbr string `env:"DICE_PROJECT_APPLICATION"` // 用于生成用户镜像repo
Expand Down
Loading

0 comments on commit 23ac102

Please sign in to comment.