Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to use multistage Docker builds based on Docker version #782

Closed
wants to merge 43 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
bf1335e
choose between multistaged and non-multistaged builds when using docker
estroz Nov 27, 2018
291abe0
Gopkg.lock: dep ensure
estroz Nov 27, 2018
14826f3
make multistage test Dockerfile with --gen-multistage
estroz Nov 27, 2018
132b89c
cp instead of symlink in tests, change alpine version
estroz Nov 27, 2018
7ee843b
.travis.yml: different way of installing docker
estroz Nov 27, 2018
3e4d9b7
increase e2e timeout
estroz Nov 27, 2018
9b48012
interanl/util/projutil/proj_util.go: refactor command exec into ExecC…
estroz Nov 28, 2018
455701a
interanl/util/projutil/proj_util.go: docker build command DockerBuild()
estroz Nov 28, 2018
e9e16e1
revendor after rebase
estroz Dec 3, 2018
a5e0e26
revendor
estroz Dec 3, 2018
f33f47b
Merge branch 'master' into choose-multistage
estroz Dec 10, 2018
1ca0c80
Merge branch 'master' into choose-multistage
estroz Dec 19, 2018
747b2b3
Merge branch 'master' into choose-multistage
estroz Dec 19, 2018
c6d4a71
Merge branch 'master' into choose-multistage
estroz Jan 2, 2019
ee4218c
add code and function comments
estroz Jan 8, 2019
33d6c66
Always scaffold multistage test Dockerfiles
estroz Jan 9, 2019
7453256
internal/util/projutil/project_util.go: better Dockerfile FROM matching
estroz Jan 9, 2019
adc83f5
Merge branch 'master' into choose-multistage
estroz Jan 12, 2019
ccf6c88
add IsNotExist check back
estroz Jan 14, 2019
2839454
Merge branch 'master' into choose-multistage
estroz Jan 14, 2019
9b18119
Merge branch 'master' into choose-multistage
estroz Jan 16, 2019
3930ad1
Merge branch 'master' into choose-multistage
estroz Jan 18, 2019
a5df713
fix log message case
estroz Jan 18, 2019
c098a43
Merge branch 'master' into choose-multistage
estroz Jan 18, 2019
664d5bd
commands/.../build.go: allow non-Go operators to scaffold test framew…
estroz Jan 19, 2019
fc8fd11
Merge branch 'master' into choose-multistage
estroz Jan 23, 2019
ff397ad
comment
estroz Jan 24, 2019
be55d69
Merge branch 'master' into choose-multistage
estroz Jan 24, 2019
6e5d726
remove log.Fatal, check errors
estroz Jan 24, 2019
52f3703
log errors instead of returning error from projutil.IsDocker{File}Mul…
estroz Jan 24, 2019
f1290f4
Merge branch 'master' into choose-multistage
estroz Feb 5, 2019
69dc2bd
fix some errors
estroz Feb 5, 2019
d811020
Merge branch 'master' into choose-multistage
estroz Feb 5, 2019
4be3f63
Merge branch 'master' into choose-multistage
estroz Feb 5, 2019
25c95ad
use build flags in non-multistage build
estroz Feb 5, 2019
bb9ab54
Merge branch 'master' into choose-multistage
estroz Feb 18, 2019
025029b
Merge branch 'master' into choose-multistage
estroz Mar 29, 2019
60d1faa
revendor
estroz Mar 29, 2019
5ed995b
cleanup
estroz Mar 29, 2019
e9da9bc
test/e2e/memcached_test.go: only copy internal/pkg if testing locally
estroz Mar 29, 2019
c623f4f
check if project is Go before tests
estroz Mar 29, 2019
660f14e
bump make test/ci-go timeout
estroz Mar 29, 2019
15277db
break up test/ci-go recipe and travis_wait on test/e2e/go
estroz Mar 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 65 additions & 7 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

158 changes: 119 additions & 39 deletions commands/operator-sdk/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
namespacedManBuild string
testLocationBuild string
enableTests bool
genMultistage bool
)

func NewBuildCmd() *cobra.Command {
Expand All @@ -58,6 +59,7 @@ For example:
Run: buildFunc,
}
buildCmd.Flags().BoolVar(&enableTests, "enable-tests", false, "Enable in-cluster testing by adding test binary to the image")
buildCmd.Flags().BoolVar(&genMultistage, "gen-multistage", false, "Generate multistage build and test Dockerfiles")
buildCmd.Flags().StringVar(&testLocationBuild, "test-location", "./test/e2e", "Location of tests")
buildCmd.Flags().StringVar(&namespacedManBuild, "namespaced-manifest", "deploy/operator.yaml", "Path of namespaced resources manifest for tests")
return buildCmd
Expand Down Expand Up @@ -141,24 +143,7 @@ func buildFunc(cmd *cobra.Command, args []string) {
if len(args) != 1 {
log.Fatalf("build command needs exactly 1 argument")
}

projutil.MustInProjectRoot()
goBuildEnv := append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
absProjectPath := projutil.MustGetwd()

// Don't need to build go code if Ansible Operator
if mainExists() {
managerDir := filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir)
outputBinName := filepath.Join(absProjectPath, scaffold.BuildBinDir, filepath.Base(absProjectPath))
buildCmd := exec.Command("go", "build", "-o", outputBinName, managerDir)
buildCmd.Env = goBuildEnv
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
err := buildCmd.Run()
if err != nil {
log.Fatalf("failed to build operator binary: (%v)", err)
}
}

image := args[0]
baseImageName := image
Expand All @@ -168,10 +153,18 @@ func buildFunc(cmd *cobra.Command, args []string) {

log.Infof("Building Docker image %s", baseImageName)

dbcmd := exec.Command("docker", "build", ".", "-f", "build/Dockerfile", "-t", baseImageName)
dbcmd.Stdout = os.Stdout
dbcmd.Stderr = os.Stderr
err := dbcmd.Run()
// The runtime environment may have docker v17.05+, in which case the SDK
// can build the binary within a container in a multistage pipeline.
// Otherwise the binary will be built on the host and COPY'd into the
// resulting image.
buildDockerfile := filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile)
buildDockerfile = makeDockerfileIfMultistage(buildDockerfile, false)
if projutil.IsOperatorGo() && !projutil.IsDockerfileMultistage(buildDockerfile) {
estroz marked this conversation as resolved.
Show resolved Hide resolved
if err := buildOperatorBinary(); err != nil {
log.Fatalf("failed to build operator binary: (%v)", err)
}
}
err := projutil.DockerBuild(buildDockerfile, baseImageName)
if err != nil {
if enableTests {
log.Fatalf("failed to output intermediate image %s: (%v)", image, err)
Expand All @@ -181,15 +174,6 @@ func buildFunc(cmd *cobra.Command, args []string) {
}

if enableTests {
testBinary := filepath.Join(absProjectPath, scaffold.BuildBinDir, filepath.Base(absProjectPath)+"-test")
buildTestCmd := exec.Command("go", "test", "-c", "-o", testBinary, testLocationBuild+"/...")
buildTestCmd.Env = goBuildEnv
buildTestCmd.Stdout = os.Stdout
buildTestCmd.Stderr = os.Stderr
err = buildTestCmd.Run()
if err != nil {
log.Fatalf("failed to build test binary: (%v)", err)
}
// if a user is using an older sdk repo as their library, make sure they have required build files
testDockerfile := filepath.Join(scaffold.BuildTestDir, scaffold.DockerfileFile)
_, err = os.Stat(testDockerfile)
Expand All @@ -206,21 +190,35 @@ func buildFunc(cmd *cobra.Command, args []string) {

s := &scaffold.Scaffold{}
err = s.Execute(cfg,
&scaffold.TestFrameworkDockerfile{},
&scaffold.TestFrameworkDockerfile{
Multistage: projutil.IsDockerMultistage(),
estroz marked this conversation as resolved.
Show resolved Hide resolved
},
&scaffold.GoTestScript{},
&scaffold.TestPod{Image: image, TestNamespaceEnv: test.TestNamespaceEnv},
&scaffold.TestPod{
Image: image,
TestNamespaceEnv: test.TestNamespaceEnv,
},
)
if err != nil {
log.Fatalf("test-framework manifest scaffold failed: (%v)", err)
}
} else if err == nil {
testDockerfile = makeDockerfileIfMultistage(testDockerfile, true)
}

log.Infof("Building test Docker image %s", image)

testDbcmd := exec.Command("docker", "build", ".", "-f", testDockerfile, "-t", image, "--build-arg", "NAMESPACEDMAN="+namespacedManBuild, "--build-arg", "BASEIMAGE="+baseImageName)
testDbcmd.Stdout = os.Stdout
testDbcmd.Stderr = os.Stderr
err = testDbcmd.Run()
// Similarly to the operator binary build, hosts that have docker < v17.05
// will build test binaries locally.
if !projutil.IsDockerfileMultistage(testDockerfile) {
if err := buildTestBinary(); err != nil {
log.Fatalf("failed to build operator binary: (%v)", err)
}
}
err = projutil.DockerBuild(testDockerfile, image,
"TESTDIR="+testLocationBuild,
"BASEIMAGE="+baseImageName,
"NAMESPACEDMAN="+namespacedManBuild)
if err != nil {
log.Fatalf("failed to output test image %s: (%v)", image, err)
}
Expand All @@ -231,7 +229,89 @@ func buildFunc(cmd *cobra.Command, args []string) {
log.Info("Operator build complete.")
}

func mainExists() bool {
_, err := os.Stat(filepath.Join(scaffold.ManagerDir, scaffold.CmdFile))
return err == nil
// makeDockerfileIfMultistage is effectively a function for migrating
// single-stage to multistage Dockerfiles. makeDockerfileIfMultistage scaffolds
// a multistage Dockerfile for Go operators on hosts with docker v17.05+ if a
// multistage Dockerfile is not already present at path dockerfile; if isTest
// is true, makeDockerfileIfMultistage will scaffold a test framework
// Dockerfile. The newly scaffolded file is named 'multistage.Dockerfile',
// which users are expected to rename to 'Dockerfile'. Users will see a warning
// if docker v17.05+ is present but they haven't set the --gen-multistage flag
// in `operator-sdk build...`
func makeDockerfileIfMultistage(dockerfile string, isTest bool) string {
if !projutil.IsOperatorGo() || !projutil.IsDockerMultistage() {
return dockerfile
}
if !projutil.IsDockerfileMultistage(dockerfile) {
msDockerfile := "multistage." + scaffold.DockerfileFile
if !genMultistage {
warnStr := `Project uses a non-multistage %sDockerfile but the present docker version
supports multistage builds. Run operator-sdk build with --gen-multistage to write
a multistage Dockerfile to '%s' and build it. Rename this
file to '%s' to avoid this warning.`
if isTest {
warnStr = fmt.Sprintf(warnStr,
"test ",
filepath.Join(scaffold.BuildTestDir, msDockerfile),
dockerfile)
} else {
warnStr = fmt.Sprintf(warnStr,
"",
filepath.Join(scaffold.BuildDir, msDockerfile),
dockerfile)
}
log.Warn(warnStr)
} else {
absProjectPath := projutil.MustGetwd()
cfg := &input.Config{
AbsProjectPath: absProjectPath,
ProjectName: filepath.Base(absProjectPath),
Repo: projutil.CheckAndGetProjectGoPkg(),
}

var f input.File
if isTest {
dockerfile = filepath.Join(scaffold.BuildTestDir, msDockerfile)
f = &scaffold.TestFrameworkDockerfile{
Multistage: true,
Input: input.Input{Path: dockerfile},
}
} else {
dockerfile = filepath.Join(scaffold.BuildDir, msDockerfile)
f = &scaffold.Dockerfile{
Multistage: true,
Input: input.Input{Path: dockerfile},
}
}
err := (&scaffold.Scaffold{}).Execute(cfg, f)
if err != nil {
log.Fatalf("failed to write %s: (%v)", msDockerfile, err)
}
}
}

return dockerfile
}

// buildOperatorBinary builds the operator binary locally.
func buildOperatorBinary() error {
absProjectPath := projutil.MustGetwd()
binName := filepath.Base(absProjectPath)
managerDir := filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir)
outputBinName := filepath.Join(absProjectPath, scaffold.BuildBinDir, binName)

cmd := exec.Command("go", "build", "-o", outputBinName, managerDir)
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
return projutil.ExecCmd(cmd)
}

// buildTestBinary builds the test framework binary locally.
func buildTestBinary() error {
absProjectPath := projutil.MustGetwd()
binName := filepath.Base(absProjectPath)
outputBinName := filepath.Join(absProjectPath, scaffold.BuildBinDir, binName+"-test")

cmd := exec.Command("go", "test", "-c", "-o", outputBinName, testLocationBuild+"/...")
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
return projutil.ExecCmd(cmd)
}
Loading