Skip to content

Commit

Permalink
Merge pull request #21 from cedrickring/support-for-build-args
Browse files Browse the repository at this point in the history
Support for build args
  • Loading branch information
cedrickring authored Dec 23, 2018
2 parents 9b56715 + 8e991bf commit 4b331ed
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 17 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ gox:
test:
go test ./...

install: all
sudo cp bin/kbuild /usr/local/bin/kbuild-dev

build-all:
mkdir -p out && cd out
which gox || make gox
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@ Specify the repo to cache build steps in (defaults to `<repository>cache`, repo
Specify namespace for the builder to run in (defaults to "default" namespace)
#### --build-arg
This flag allows you to pass in build args (ARG) for the Kaniko executor
### How does kbuild work?
In order to use the local context, the context needs to be tar-ed, copied to an Init Container, which shares an
empty volume with the Kaniko container, and extracted in the empty volume.
### Limitations
* Build args are not supported
* You cannot specify args for the Kaniko executor
#### Windows
Expand Down
11 changes: 7 additions & 4 deletions cmd/kbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import (
var (
dockerfile string
workingDir string
imageTags []string
useCache bool
cacheRepo string
namespace string
imageTags []string
buildArgs []string
useCache bool
)

func main() {
Expand All @@ -44,10 +45,11 @@ func main() {
}
rootCmd.Flags().StringVarP(&dockerfile, "dockerfile", "d", "Dockerfile", "Path to Dockerfile inside working directory")
rootCmd.Flags().StringVarP(&workingDir, "workdir", "w", ".", "Working directory")
rootCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "The namespace to run the build in")
rootCmd.Flags().StringVarP(&cacheRepo, "cache-repo", "", "", "Repository for cached images (see --cache)")
rootCmd.Flags().StringSliceVarP(&imageTags, "tag", "t", nil, "Final image tag(s) (required)")
rootCmd.Flags().StringSliceVarP(&buildArgs, "build-arg", "", nil, "Optional build arguments (ARG)")
rootCmd.Flags().BoolVarP(&useCache, "cache", "c", false, "Enable RUN command caching")
rootCmd.Flags().StringVarP(&cacheRepo, "cache-repo", "", "", "Repository for cached images (see --cache)")
rootCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "The namespace to run the build in")
rootCmd.MarkFlagRequired("tag")

rootCmd.Execute()
Expand Down Expand Up @@ -82,6 +84,7 @@ func run(_ *cobra.Command, _ []string) {
Cache: useCache,
CacheRepo: cacheRepo,
Namespace: namespace,
BuildArgs: buildArgs,
}
err := b.StartBuild()
if err != nil {
Expand Down
7 changes: 4 additions & 3 deletions example/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
FROM golang:alpine as build-env
ARG MAIN=main.go
COPY . /src/
RUN cd /src && go build -o helloworld main.go
RUN cd /src && go build -o helloworld $MAIN

FROM alpine
FROM scratch
WORKDIR /app
COPY --from=build-env /src/helloworld /app/
CMD ["/app/helloworld"]
ENTRYPOINT ["/app/helloworld"]
4 changes: 2 additions & 2 deletions pkg/docker/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ import (
)

//CreateContextFromWorkingDir creates a build context of the provided directory and writes it to the Writer
func CreateContextFromWorkingDir(workDir, dockerfile string, w io.Writer) error {
func CreateContextFromWorkingDir(workDir, dockerfile string, w io.Writer, buildArgs []string) error {
if _, err := os.Stat(workDir); os.IsNotExist(err) {
return errors.Wrap(err, "get context from workDir")
}

paths, err := GetFilePaths(workDir, dockerfile) //paths are relative to the directory this executable runs in
paths, err := GetFilePaths(workDir, dockerfile, buildArgs) //paths are relative to the directory this executable runs in
if err != nil {
return err
}
Expand Down
75 changes: 71 additions & 4 deletions pkg/docker/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

//GetFilePaths returns all paths required to build the docker image
func GetFilePaths(workDir, dockerfile string) ([]string, error) {
func GetFilePaths(workDir, dockerfile string, buildArgs []string) ([]string, error) {
dfPath := strings.Replace(filepath.Join(workDir, dockerfile), "\\", "/", -1)
f, err := os.Open(dfPath)
if err != nil {
Expand All @@ -46,17 +46,27 @@ func GetFilePaths(workDir, dockerfile string) ([]string, error) {

children := result.AST.Children

envVars := map[string]string{}
envVars := make(map[string]string)
args, err := parseBuildArgs(buildArgs)
if err != nil {
return nil, errors.Wrap(err, "parsing build args from flags")
}

for _, node := range children {
switch node.Value {
case command.Copy, command.Add:
parsed, err := parseCopyOrAdd(workDir, node, envVars)
parsed, err := parseCopyOrAdd(workDir, node, envVars, args)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("Dockerfile line %d", node.StartLine))
}
paths = append(paths, parsed...)
case command.Env:
envVars[node.Next.Value] = node.Next.Next.Value
case command.Arg:
err := parseArgCommand(node, args)
if err != nil {
return nil, err
}
}
}

Expand All @@ -65,7 +75,7 @@ func GetFilePaths(workDir, dockerfile string) ([]string, error) {
return paths, nil
}

func parseCopyOrAdd(wd string, node *parser.Node, envVars map[string]string) ([]string, error) {
func parseCopyOrAdd(wd string, node *parser.Node, envVars map[string]string, buildArgs map[string]string) ([]string, error) {
var paths []string

for _, flag := range node.Flags {
Expand Down Expand Up @@ -94,6 +104,18 @@ func parseCopyOrAdd(wd string, node *parser.Node, envVars map[string]string) ([]
}
abs := strings.Replace(filepath.Join(wd, node.Value), "\\", "/", -1) //need forward-slashes in windows so they don't get escaped by lex

for key, value := range buildArgs {
if _, ok := envVars[key]; ok {
continue //ENV variables always override ARG variables
}

r := regexp.MustCompile(`(\$` + key + `|\${` + key + `})`)
submatch := r.FindStringSubmatch(abs)
if len(submatch) > 0 {
abs = r.ReplaceAllString(abs, value)
}
}

expanded, err := lex.ProcessWordWithMap(abs, envVars)
if err != nil {
return nil, err
Expand Down Expand Up @@ -125,3 +147,48 @@ func parseCopyOrAdd(wd string, node *parser.Node, envVars map[string]string) ([]

return paths, nil
}

func parseArgCommand(node *parser.Node, args map[string]string) error {
arg := node.Next.Value
if _, ok := args[arg]; ok {
return nil //skip if arg is set by flag, otherwise check for default value
}

if !strings.Contains(arg, "=") {
if _, ok := args[arg]; !ok {
return errors.Errorf("required arg %s was not set by --arg flag", arg)
}
return nil
}

key, value, err := parseArg(arg)
if err != nil {
return errors.Wrap(err, "parsing ARG command")
}
args[key] = value
return nil
}

func parseBuildArgs(buildArgs []string) (map[string]string, error) {
args := make(map[string]string)

for _, arg := range buildArgs {
key, value, err := parseArg(arg)
if err != nil {
return args, err
}
args[key] = value
}

return args, nil
}

func parseArg(arg string) (key, value string, err error) {
split := strings.Split(arg, "=")
if len(split) != 2 {
return "", "", errors.New("invalid arg format, must be ARG=VALUE")
}
key = split[0]
value = split[1]
return key, value, nil
}
57 changes: 55 additions & 2 deletions pkg/docker/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,41 @@ func TestGetFilePaths(t *testing.T) {
var tests = []struct {
dockerfile string
expectedPaths []string
buildArgs []string
shouldErr bool
}{
{
dockerfile: "Dockerfile.test",
expectedPaths: []string{"test/test.go", "test/Dockerfile.test"},
shouldErr: false,
},
{
dockerfile: "Dockerfile.dot-test",
expectedPaths: []string{"test", "test/Dockerfile.dot-test"},
shouldErr: false,
},
{
dockerfile: "Dockerfile.arg-test",
expectedPaths: []string{"test/test.go", "test/other.go", "test/Dockerfile.arg-test"},
buildArgs: []string{"TEST=test.go", "OTHER=other.go"},
shouldErr: false,
},
{
dockerfile: "Dockerfile.arg-test",
shouldErr: true,
},
{
dockerfile: "Dockerfile.arg-env-test",
expectedPaths: []string{"test/test.go", "test/Dockerfile.arg-env-test"},
},
}

for _, test := range tests {
paths, err := GetFilePaths("test", test.dockerfile)
paths, err := GetFilePaths("test", test.dockerfile, test.buildArgs)
if err != nil {
t.Errorf("Couldn't get file paths: %s", err.Error())
if !test.shouldErr {
t.Errorf("Couldn't get file paths: %s", err.Error())
}
continue
}

Expand All @@ -33,3 +53,36 @@ func TestGetFilePaths(t *testing.T) {
}
}
}

func TestGetBuildArgs(t *testing.T) {
var tests = []struct {
buildArgs []string
argsMap map[string]string
shouldErr bool
}{
{
buildArgs: []string{"ARG1=VALUE", "ARG2=VALUE2"},
argsMap: map[string]string{"ARG1": "VALUE", "ARG2": "VALUE2"},
shouldErr: false,
},
{
buildArgs: []string{"no-key-value"},
argsMap: make(map[string]string),
shouldErr: true,
},
}

for _, test := range tests {
args, err := parseBuildArgs(test.buildArgs)
if err != nil {
if !test.shouldErr {
t.Errorf("Expected parseBuildArgs not to error but got error %s", err)
}
continue
}

if args == nil || !reflect.DeepEqual(args, test.argsMap) {
t.Errorf("Expected %s but got %s", test.argsMap, args)
}
}
}
6 changes: 6 additions & 0 deletions pkg/docker/test/Dockerfile.arg-env-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM scratch

ARG TEST=skipped
ENV TEST=test.go

COPY $TEST .
6 changes: 6 additions & 0 deletions pkg/docker/test/Dockerfile.arg-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM scratch
ARG TEST
ARG OTHER

COPY $TEST .
COPY ${OTHER} .
1 change: 1 addition & 0 deletions pkg/docker/test/other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package test
3 changes: 2 additions & 1 deletion pkg/kaniko/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Build struct {
Cache bool
CacheRepo string
Namespace string
BuildArgs []string

tarPath string
}
Expand Down Expand Up @@ -136,7 +137,7 @@ func (b *Build) generateContext() (func(), error) {
}
defer file.Close()

err = docker.CreateContextFromWorkingDir(b.WorkDir, b.DockerfilePath, file)
err = docker.CreateContextFromWorkingDir(b.WorkDir, b.DockerfilePath, file, b.BuildArgs)
if err != nil {
return nil, errors.Wrap(err, "generating context")
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/kaniko/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ func (b Build) getKanikoPod() *v1.Pod {
pod.Spec.Containers[0].Args = append(pod.Spec.Containers[0].Args, fmt.Sprintf("--destination=%s", tag))
}

//add all build args to Kaniko container args
for _, arg := range b.BuildArgs {
pod.Spec.Containers[0].Args = append(pod.Spec.Containers[0].Args, fmt.Sprintf("--build-arg=%s", arg))
}

//only enable caching if provided by "kbuild --cache"
if b.Cache {
cacheRepo := b.CacheRepo
Expand Down

0 comments on commit 4b331ed

Please sign in to comment.