diff --git a/buildtree/buildtree.go b/buildtree/buildtree.go index 8ccbf6e..2371143 100644 --- a/buildtree/buildtree.go +++ b/buildtree/buildtree.go @@ -18,6 +18,7 @@ import ( // BuildTree is a build tree type BuildTree struct { + rootDir string rootNodes []*buildNode allNodes map[string]*buildNode credentials map[string]*credentialConfig @@ -28,6 +29,8 @@ type buildNode struct { name string tag string depend string + preBuild string + postBuild string children []*buildNode dirty bool forceBuild bool @@ -79,18 +82,21 @@ func readBuildTree(configFilePath string, fileContent []byte, variableMap map[st if err != nil { return nil, err } + configFileFolder := filepath.Dir(configFilePath) buildTree := &BuildTree{ + rootDir: filepath.Join(configFileFolder, buildConfig.RootDir), rootNodes: []*buildNode{}, allNodes: make(map[string]*buildNode), credentials: make(map[string]*credentialConfig), } - configFileFolder := filepath.Dir(configFilePath) for _, buildNodeConfig := range buildConfig.Build { node := &buildNode{ - buildRoot: utils.ResolveDir(filepath.Join(configFileFolder, buildConfig.RootDir), buildNodeConfig.From), + buildRoot: utils.ResolveDir(buildTree.rootDir, buildNodeConfig.From), name: utils.FormatDockerName(buildNodeConfig.Name), tag: buildNodeConfig.Tag, depend: utils.FormatDockerName(buildNodeConfig.Depend), + preBuild: buildNodeConfig.PreBuild, + postBuild: buildNodeConfig.PostBuild, children: []*buildNode{}, dirty: false, forceBuild: buildNodeConfig.ForceBuild, @@ -313,7 +319,7 @@ func (t *BuildTree) buildNodeAndChildren(node *buildNode) error { fmt.Printf("====> Skipping %s\n", node.name) } else { fmt.Printf("====> Building %s:%s\n", node.name, node.tag) - err := utils.DockerBuild(node.name, node.tag, node.buildRoot) + err := t.buildNode(node, node.tag) if err != nil { return err } @@ -333,7 +339,7 @@ func (t *BuildTree) tryBuildNodeAndChildren(node *buildNode) error { } else { randomTag := fmt.Sprintf("%s-%d", node.tag, time.Now().UnixNano()) fmt.Printf("====> Building %s:%s\n", node.name, randomTag) - err := utils.DockerBuild(node.name, randomTag, node.buildRoot) + err := t.buildNode(node, randomTag) if err != nil { return err } @@ -352,6 +358,30 @@ func (t *BuildTree) tryBuildNodeAndChildren(node *buildNode) error { return nil } +func (t *BuildTree) buildNode(node *buildNode, tag string) error { + if node.preBuild != "" { + err := utils.RunShellCommand(t.resolveShellCommandPath(t.rootDir, node.preBuild)) + if err != nil { + return err + } + } + err := utils.DockerBuild(node.name, tag, node.buildRoot) + if node.postBuild != "" { + utils.RunShellCommand(t.resolveShellCommandPath(t.rootDir, node.postBuild)) + } + return err +} + +func (t *BuildTree) resolveShellCommandPath(buildRoot, command string) string { + if strings.HasPrefix(command, "/") { + return command + } + if strings.HasPrefix(command, "./") || strings.HasPrefix(command, "../") { + return buildRoot + "/" + command + } + return command +} + func (t *BuildTree) pushNodeAndChildren(node *buildNode) error { if !t.needBuild(node) { fmt.Printf("====> Skipping %s\n", node.name) diff --git a/buildtree/buildtree_test.go b/buildtree/buildtree_test.go index 0494bbf..edb0d93 100644 --- a/buildtree/buildtree_test.go +++ b/buildtree/buildtree_test.go @@ -2,10 +2,12 @@ package buildtree import ( "os" + "os/exec" "path/filepath" "sort" "testing" + "github.com/anduintransaction/doriath/utils" "github.com/palantir/stacktrace" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -76,7 +78,7 @@ credentials: } func (s *BuildTreeTestSuite) TestBuildTreeHappyPath() { - if !s.checkIntegTestEnable() { + if !s.checkDockerhubTestEnable() { s.T().Log("Skipping test happy path") return } @@ -189,7 +191,7 @@ func (s *BuildTreeTestSuite) TestMismatchTag() { } func (s *BuildTreeTestSuite) TestMissingProvidedImage() { - if !s.checkIntegTestEnable() { + if !s.checkDockerhubTestEnable() { s.T().Log("Skipping test missing provided image") return } @@ -202,7 +204,7 @@ func (s *BuildTreeTestSuite) TestMissingProvidedImage() { } func (s *BuildTreeTestSuite) TestOutdateTag() { - if !s.checkIntegTestEnable() { + if !s.checkDockerhubTestEnable() { s.T().Log("Skipping test outdate tag") return } @@ -214,7 +216,26 @@ func (s *BuildTreeTestSuite) TestOutdateTag() { require.True(s.T(), ok) } -func (s *BuildTreeTestSuite) checkIntegTestEnable() bool { +func (s *BuildTreeTestSuite) TestPrePostBuild() { + if !s.checkDockerTestEnable() { + s.T().Log("Skipping testing pre-post build") + return + } + rootFolder := filepath.Join(s.resourceFolder, "pre-and-post-build") + buildTree, err := ReadBuildTreeFromFile(filepath.Join(rootFolder, "doriath.yml"), map[string]string{}) + require.Nil(s.T(), err, "build tree must be readable") + err = buildTree.Prepare() + require.Nil(s.T(), err, "build tree must be able to be prepared") + err = buildTree.Build() + require.Nil(s.T(), err, "build tree must be able to be built") + cmd := exec.Command("docker", "run", "--rm", "node1:1.0") + output, err := cmd.Output() + require.Nil(s.T(), err, "docker must run successfully") + require.Equal(s.T(), "42\n", string(output)) + utils.RunShellCommand("docker rmi node1:1.0") +} + +func (s *BuildTreeTestSuite) checkDockerhubTestEnable() bool { dockerhubTestEnv := os.Getenv("DOCKERHUB_TEST_ENABLE") if dockerhubTestEnv == "1" || dockerhubTestEnv == "true" { if os.Getenv("DOCKERHUB_USERNAME") == "" { @@ -228,6 +249,11 @@ func (s *BuildTreeTestSuite) checkIntegTestEnable() bool { return false } +func (s *BuildTreeTestSuite) checkDockerTestEnable() bool { + dockerTestEnv := os.Getenv("DOCKER_TEST_ENABLE") + return dockerTestEnv == "1" || dockerTestEnv == "true" +} + type buildNodeForTestData struct { buildRoot string name string diff --git a/test-resources/pre-and-post-build/.gitignore b/test-resources/pre-and-post-build/.gitignore new file mode 100644 index 0000000..29d3de0 --- /dev/null +++ b/test-resources/pre-and-post-build/.gitignore @@ -0,0 +1 @@ +node1/data \ No newline at end of file diff --git a/test-resources/pre-and-post-build/doriath.yml b/test-resources/pre-and-post-build/doriath.yml new file mode 100644 index 0000000..2b4c8d0 --- /dev/null +++ b/test-resources/pre-and-post-build/doriath.yml @@ -0,0 +1,8 @@ +root_dir: . +build: + - name: node1 + tag: 1.0 + from: ./node1 + prebuild: ./scripts/prebuild.sh + postbuild: ./scripts/postbuild.sh + forcebuild: true diff --git a/test-resources/pre-and-post-build/node1/Dockerfile b/test-resources/pre-and-post-build/node1/Dockerfile new file mode 100644 index 0000000..86a3989 --- /dev/null +++ b/test-resources/pre-and-post-build/node1/Dockerfile @@ -0,0 +1,6 @@ +FROM ubuntu:16.04 + +ADD run.sh /opt/run.sh +ADD data /opt/data + +CMD exec /opt/run.sh diff --git a/test-resources/pre-and-post-build/node1/run.sh b/test-resources/pre-and-post-build/node1/run.sh new file mode 100755 index 0000000..ab7e829 --- /dev/null +++ b/test-resources/pre-and-post-build/node1/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cat /opt/data diff --git a/test-resources/pre-and-post-build/scripts/postbuild.sh b/test-resources/pre-and-post-build/scripts/postbuild.sh new file mode 100755 index 0000000..06394cf --- /dev/null +++ b/test-resources/pre-and-post-build/scripts/postbuild.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +here=`cd $(dirname $BASH_SOURCE); pwd` +rm -f $here/../node1/data diff --git a/test-resources/pre-and-post-build/scripts/prebuild.sh b/test-resources/pre-and-post-build/scripts/prebuild.sh new file mode 100755 index 0000000..dfed563 --- /dev/null +++ b/test-resources/pre-and-post-build/scripts/prebuild.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +here=`cd $(dirname $BASH_SOURCE); pwd` +echo 42 > $here/../node1/data diff --git a/utils/common.go b/utils/common.go index 3419d4a..468b572 100644 --- a/utils/common.go +++ b/utils/common.go @@ -59,3 +59,11 @@ func (s StringSet) Exists(value string) bool { _, ok := s[value] return ok } + +// RunShellCommand runs a command under bash shell +func RunShellCommand(command string) error { + cmd := exec.Command("sh", "-c", command) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +}