From 599e47c8c22e8e47890ad20756dc7b47a3aac7a3 Mon Sep 17 00:00:00 2001 From: Mojtaba Date: Mon, 30 Sep 2024 18:47:36 +0330 Subject: [PATCH] feat: add support for build arguments when building Docker images using Kaniko --- ...d_from_git_test.go => build_image_test.go} | 44 ++++++++++++- pkg/builder/args.go | 39 ++++++++++++ pkg/builder/builder.go | 2 +- pkg/builder/kaniko/kaniko.go | 61 +++++++++++-------- pkg/builder/kaniko/kaniko_test.go | 2 +- pkg/container/docker.go | 6 +- pkg/instance/build.go | 8 +-- 7 files changed, 127 insertions(+), 35 deletions(-) rename e2e/system/{build_from_git_test.go => build_image_test.go} (68%) create mode 100644 pkg/builder/args.go diff --git a/e2e/system/build_from_git_test.go b/e2e/system/build_image_test.go similarity index 68% rename from e2e/system/build_from_git_test.go rename to e2e/system/build_image_test.go index add4297b..28014656 100644 --- a/e2e/system/build_from_git_test.go +++ b/e2e/system/build_image_test.go @@ -2,6 +2,7 @@ package system import ( "context" + "fmt" "strings" "github.com/celestiaorg/knuu/pkg/builder" @@ -73,8 +74,6 @@ func (s *Suite) TestBuildFromGitWithModifications() { }) s.Require().NoError(err) - s.Require().NoError(target.Build().SetStartCommand("sleep", "infinity")) - const ( filePath = "/home/hello.txt" expectedData = "Hello, world!" @@ -92,3 +91,44 @@ func (s *Suite) TestBuildFromGitWithModifications() { s.Assert().Equal([]byte(expectedData), gotData, "file bytes do not match.") } + +func (s *Suite) TestBuildWithBuildArgs() { + const ( + namePrefix = "build-from-git-with-build-args" + maxRetries = 3 + + // This file is created by the dockerfile in the repo + // ref: https://github.com/celestiaorg/knuu/blob/test/build-from-git/Dockerfile + filePath = "/test.txt" + expectedData = "Hello, build arg!" + ) + + s.T().Log("Creating new instance") + target, err := s.Knuu.NewInstance(namePrefix) + s.Require().NoError(err) + + s.T().Log("Setting git repo") + ctx := context.Background() + err = target.Build().SetGitRepo(ctx, + builder.GitContext{ + Repo: gitRepo, + Branch: gitBranch, + Username: "", + Password: "", + }, + &builder.BuildArg{ + Value: fmt.Sprintf("MESSAGE=%s", expectedData), + }, + ) + s.Require().NoError(err) + s.Require().NoError(target.Build().Commit(ctx)) + + s.Require().NoError(target.Execution().Start(ctx)) + + gotData, err := target.Storage().GetFileBytes(ctx, filePath) + s.Require().NoError(err) + + gotData = []byte(strings.TrimSpace(string(gotData))) + + s.Assert().Equal([]byte(expectedData), gotData, "file bytes do not match.") +} diff --git a/pkg/builder/args.go b/pkg/builder/args.go new file mode 100644 index 00000000..4a2d733d --- /dev/null +++ b/pkg/builder/args.go @@ -0,0 +1,39 @@ +package builder + +const buildArgKey = "--build-arg" + +type ArgInterface interface { + GetKey() string + GetValue() string +} + +// BuildArg is a build argument that can be passed to the builder. +type BuildArg struct { + Value string +} + +var _ ArgInterface = &BuildArg{} + +func (b *BuildArg) GetKey() string { + return buildArgKey +} + +func (b *BuildArg) GetValue() string { + return b.Value +} + +// CustomArg is a custom argument that can be passed to the builder. +type CustomArg struct { + Key string + Value string +} + +var _ ArgInterface = &CustomArg{} + +func (c *CustomArg) GetKey() string { + return c.Key +} + +func (c *CustomArg) GetValue() string { + return c.Value +} diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go index d30e56bb..36b81dd7 100644 --- a/pkg/builder/builder.go +++ b/pkg/builder/builder.go @@ -14,7 +14,7 @@ type Builder interface { type BuilderOptions struct { ImageName string BuildContext string - Args []string + Args []ArgInterface Destination string Cache *CacheOptions } diff --git a/pkg/builder/kaniko/kaniko.go b/pkg/builder/kaniko/kaniko.go index 026590b4..b187da77 100644 --- a/pkg/builder/kaniko/kaniko.go +++ b/pkg/builder/kaniko/kaniko.go @@ -143,8 +143,10 @@ func (k *Kaniko) prepareJob(ctx context.Context, b *builder.BuilderOptions) (*ba return nil, ErrParsingQuantity.Wrap(err) } - parallelism := DefaultParallelism - backoffLimit := DefaultBackoffLimit + var ( + parallelism = DefaultParallelism + backoffLimit = DefaultBackoffLimit + ) job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, @@ -158,15 +160,7 @@ func (k *Kaniko) prepareJob(ctx context.Context, b *builder.BuilderOptions) (*ba { Name: kanikoContainerName, Image: kanikoImage, // debug has a shell - Args: []string{ - `--context=` + b.BuildContext, - // TODO: see if we need it or not - // --git gitoptions Branch to clone if build context is a git repository (default branch=,single-branch=false,recurse-submodules=false) - - // TODO: we might need to add some options to get the auth token for the registry - "--destination=" + b.Destination, - // "--verbosity=debug", // log level - }, + Args: prepareArgs(b), Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceEphemeralStorage: ephemeralStorage, @@ -187,21 +181,6 @@ func (k *Kaniko) prepareJob(ctx context.Context, b *builder.BuilderOptions) (*ba } } - // TODO: we need to add some configs to get the auth token for the cache repo - if b.Cache != nil && b.Cache.Enabled { - cacheArgs := []string{"--cache=true"} - if b.Cache.Dir != "" { - cacheArgs = append(cacheArgs, "--cache-dir="+b.Cache.Dir) - } - if b.Cache.Repo != "" { - cacheArgs = append(cacheArgs, "--cache-repo="+b.Cache.Repo) - } - job.Spec.Template.Spec.Containers[0].Args = append(job.Spec.Template.Spec.Containers[0].Args, cacheArgs...) - } - - // Add extra args - job.Spec.Template.Spec.Containers[0].Args = append(job.Spec.Template.Spec.Containers[0].Args, b.Args...) - return job, nil } @@ -272,3 +251,33 @@ func (k *Kaniko) mountDir(ctx context.Context, bCtx string, job *batchv1.Job) (* return job, nil } + +func prepareArgs(b *builder.BuilderOptions) []string { + args := []string{ + "--context=" + b.BuildContext, + // TODO: see if we need it or not + // --git gitoptions Branch to clone if build context is a git repository (default branch=,single-branch=false,recurse-submodules=false) + + // TODO: we might need to add some options to get the auth token for the registry + "--destination=" + b.Destination, + // "--verbosity=debug", // log level + } + + // TODO: we need to add some configs to get the auth token for the cache repo + if b.Cache != nil && b.Cache.Enabled { + args = append(args, "--cache=true") + if b.Cache.Dir != "" { + args = append(args, "--cache-dir="+b.Cache.Dir) + } + if b.Cache.Repo != "" { + args = append(args, "--cache-repo="+b.Cache.Repo) + } + } + + // Append other args e.g. build args + for _, a := range b.Args { + args = append(args, fmt.Sprintf("%s=%s", a.GetKey(), a.GetValue())) + } + + return args +} diff --git a/pkg/builder/kaniko/kaniko_test.go b/pkg/builder/kaniko/kaniko_test.go index 8b75b378..2006a6fe 100644 --- a/pkg/builder/kaniko/kaniko_test.go +++ b/pkg/builder/kaniko/kaniko_test.go @@ -46,7 +46,7 @@ func TestKanikoBuilder(t *testing.T) { ImageName: testImage, BuildContext: blCtx, Destination: testDestination, - Args: []string{"--build-arg=value"}, + Args: []builder.ArgInterface{&builder.BuildArg{Value: "SOME_ARG=some_value"}}, Cache: cacheOpts, } diff --git a/pkg/container/docker.go b/pkg/container/docker.go index 6457785e..ec35d50e 100644 --- a/pkg/container/docker.go +++ b/pkg/container/docker.go @@ -21,10 +21,11 @@ type BuilderFactory struct { imageBuilder builder.Builder dockerFileInstructions []string buildContext string + args []builder.ArgInterface } // NewBuilderFactory creates a new instance of BuilderFactory. -func NewBuilderFactory(imageName, buildContext string, imageBuilder builder.Builder) (*BuilderFactory, error) { +func NewBuilderFactory(imageName, buildContext string, imageBuilder builder.Builder, args []builder.ArgInterface) (*BuilderFactory, error) { if err := os.MkdirAll(buildContext, 0755); err != nil { return nil, ErrFailedToCreateContextDir.Wrap(err) } @@ -34,6 +35,7 @@ func NewBuilderFactory(imageName, buildContext string, imageBuilder builder.Buil dockerFileInstructions: []string{"FROM " + imageName}, buildContext: buildContext, imageBuilder: imageBuilder, + args: args, }, nil } @@ -96,6 +98,7 @@ func (f *BuilderFactory) PushBuilderImage(ctx context.Context, imageName string) ImageName: f.imageNameTo, Destination: f.imageNameTo, // in docker the image name and destination are the same BuildContext: builder.DirContext{Path: f.buildContext}.BuildContext(), + Args: f.args, }) qStatus := logrus.TextFormatter{}.DisableQuote @@ -133,6 +136,7 @@ func (f *BuilderFactory) BuildImageFromGitRepo(ctx context.Context, gitCtx build Destination: imageName, BuildContext: buildCtx, Cache: cOpts, + Args: f.args, }) qStatus := logrus.TextFormatter{}.DisableQuote diff --git a/pkg/instance/build.go b/pkg/instance/build.go index 1a3ad1c9..ac9c96e7 100644 --- a/pkg/instance/build.go +++ b/pkg/instance/build.go @@ -45,7 +45,7 @@ func (b *build) SetImagePullPolicy(pullPolicy v1.PullPolicy) { // SetImage sets the image of the instance. // It is only allowed in the 'None' and 'Preparing' states. -func (b *build) SetImage(ctx context.Context, image string) error { +func (b *build) SetImage(ctx context.Context, image string, args ...builder.ArgInterface) error { if !b.instance.IsInState(StateNone, StatePreparing, StateStopped) { if b.instance.sidecars.IsSidecar() { return ErrSettingImageNotAllowedForSidecarsStarted @@ -54,7 +54,7 @@ func (b *build) SetImage(ctx context.Context, image string) error { } // Use the builder to build a new image - factory, err := container.NewBuilderFactory(image, b.getBuildDir(), b.instance.ImageBuilder) + factory, err := container.NewBuilderFactory(image, b.getBuildDir(), b.instance.ImageBuilder, args) if err != nil { return ErrCreatingBuilder.Wrap(err) } @@ -66,7 +66,7 @@ func (b *build) SetImage(ctx context.Context, image string) error { // SetGitRepo builds the image from the given git repo, pushes it // to the registry under the given name and sets the image of the instance. -func (b *build) SetGitRepo(ctx context.Context, gitContext builder.GitContext) error { +func (b *build) SetGitRepo(ctx context.Context, gitContext builder.GitContext, args ...builder.ArgInterface) error { if !b.instance.IsState(StateNone) { return ErrSettingGitRepo.WithParams(b.instance.state.String()) } @@ -80,7 +80,7 @@ func (b *build) SetGitRepo(ctx context.Context, gitContext builder.GitContext) e return ErrGettingImageName.Wrap(err) } - factory, err := container.NewBuilderFactory(imageName, b.getBuildDir(), b.instance.ImageBuilder) + factory, err := container.NewBuilderFactory(imageName, b.getBuildDir(), b.instance.ImageBuilder, args) if err != nil { return ErrCreatingBuilder.Wrap(err) }