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 40 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
76 changes: 72 additions & 4 deletions Gopkg.lock

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

157 changes: 108 additions & 49 deletions cmd/operator-sdk/build/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/operator-framework/operator-sdk/internal/pkg/scaffold"
Expand All @@ -39,6 +40,7 @@ var (
testLocationBuild string
enableTests bool
dockerBuildArgs string
genMultistage bool
)

func NewCmd() *cobra.Command {
Expand All @@ -60,6 +62,7 @@ For example:
RunE: 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")
buildCmd.Flags().StringVar(&dockerBuildArgs, "docker-build-args", "", "Extra docker build arguments as one string such as \"--build-arg https_proxy=$https_proxy\"")
Expand Down Expand Up @@ -145,78 +148,70 @@ func buildFunc(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("command %s requires exactly one argument", cmd.CommandPath())
}

projutil.MustInProjectRoot()
goBuildEnv := append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
goTrimFlags := []string{"-gcflags", "all=-trimpath=${GOPATH}", "-asmflags", "all=-trimpath=${GOPATH}"}
absProjectPath := projutil.MustGetwd()
projectName := filepath.Base(absProjectPath)

// Don't need to build Go code if a non-Go Operator.
if projutil.GetOperatorType() == projutil.OperatorTypeGo {
managerDir := filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir)
outputBinName := filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName)
goBuildArgs := append(append([]string{"build"}, goTrimFlags...), "-o", outputBinName, managerDir)
buildCmd := exec.Command("go", goBuildArgs...)
buildCmd.Env = goBuildEnv
if err := projutil.ExecCmd(buildCmd); err != nil {
return fmt.Errorf("failed to build operator binary: (%v)", err)
}
}

image := args[0]
baseImageName := image
if enableTests {
baseImageName += "-intermediate"
}
var dbArgs []string
if dockerBuildArgs != "" {
splitArgs := regexp.MustCompile("--build-arg(=)?").ReplaceAllString(dockerBuildArgs, "")
dbArgs = strings.Fields(splitArgs)
}
absProjectPath := projutil.MustGetwd()
projectName := filepath.Base(absProjectPath)

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

dbArgs := []string{"build", ".", "-f", "build/Dockerfile", "-t", baseImageName}

if dockerBuildArgs != "" {
splitArgs := strings.Fields(dockerBuildArgs)
dbArgs = append(dbArgs, splitArgs...)
// 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, err := makeDockerfileIfMultistage(filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile))
if err != nil {
return err
}

dbcmd := exec.Command("docker", dbArgs...)
if err := projutil.ExecCmd(dbcmd); err != nil {
if projutil.IsOperatorGo() && !projutil.IsDockerfileMultistage(buildDockerfile) {
if err := buildOperatorBinary(); err != nil {
return fmt.Errorf("failed to build operator binary: (%v)", err)
}
}
err = projutil.DockerBuild(buildDockerfile, baseImageName, dbArgs...)
if err != nil {
if enableTests {
return fmt.Errorf("failed to output intermediate image %s: (%v)", image, err)
}
return fmt.Errorf("failed to output build image %s: (%v)", image, err)
}

if enableTests {
if projutil.GetOperatorType() == projutil.OperatorTypeGo {
testBinary := filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName+"-test")
goTestBuildArgs := append(append([]string{"test"}, goTrimFlags...), "-c", "-o", testBinary, testLocationBuild+"/...")
buildTestCmd := exec.Command("go", goTestBuildArgs...)
buildTestCmd.Env = goBuildEnv
if err := projutil.ExecCmd(buildTestCmd); err != nil {
return fmt.Errorf("failed to build test binary: (%v)", err)
}
if !projutil.IsDockerMultistage() {
return fmt.Errorf("in-cluster tests are only available on hosts with Docker v17.05+")
}

// if a user is using an older sdk repo as their library, make sure they have required build files
// If a user is using an older sdk repo as their library, make sure they
// have the required build files.
testDockerfile := filepath.Join(scaffold.BuildTestDir, scaffold.DockerfileFile)
_, err := os.Stat(testDockerfile)
if err != nil && os.IsNotExist(err) {
_, err = os.Stat(testDockerfile)
if (err != nil && os.IsNotExist(err)) || !projutil.IsDockerfileMultistage(testDockerfile) {

log.Info("Generating build manifests for test-framework.")

cfg := &input.Config{
Repo: projutil.CheckAndGetProjectGoPkg(),
AbsProjectPath: absProjectPath,
ProjectName: projectName,
}

s := &scaffold.Scaffold{}
t := projutil.GetOperatorType()
switch t {
switch t := projutil.GetOperatorType(); t {
case projutil.OperatorTypeGo:
cfg.Repo = projutil.CheckAndGetProjectGoPkg()
err = s.Execute(cfg,
&scaffold.TestFrameworkDockerfile{},
&scaffold.TestFrameworkDockerfile{
Input: input.Input{IfExistsAction: input.Overwrite},
},
&scaffold.GoTestScript{},
&scaffold.TestPod{Image: image, TestNamespaceEnv: test.TestNamespaceEnv},
)
Expand All @@ -235,15 +230,14 @@ func buildFunc(cmd *cobra.Command, args []string) error {

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

testDbArgs := []string{"build", ".", "-f", testDockerfile, "-t", image, "--build-arg", "NAMESPACEDMAN=" + namespacedManBuild, "--build-arg", "BASEIMAGE=" + baseImageName}

if dockerBuildArgs != "" {
splitArgs := strings.Fields(dockerBuildArgs)
testDbArgs = append(testDbArgs, splitArgs...)
}

testDbcmd := exec.Command("docker", testDbArgs...)
if err := projutil.ExecCmd(testDbcmd); err != nil {
// Tests require docker v17.05+ anyway so we don't need to conditionally
// scaffold a multistage Dockerfile.
err = projutil.DockerBuild(testDockerfile, image,
append(dbArgs,
"TESTDIR="+testLocationBuild,
"BASEIMAGE="+baseImageName,
"NAMESPACEDMAN="+namespacedManBuild)...)
if err != nil {
return fmt.Errorf("failed to output test image %s: (%v)", image, err)
}
// Check image name of deployments in namespaced manifest
Expand All @@ -255,3 +249,68 @@ func buildFunc(cmd *cobra.Command, args []string) error {
log.Info("Operator build complete.")
return 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. 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) (string, error) {
if !projutil.IsOperatorGo() || !projutil.IsDockerMultistage() {
return dockerfile, nil
}

if !projutil.IsDockerfileMultistage(dockerfile) {
msDockerfile := "multistage." + scaffold.DockerfileFile
if !genMultistage {
log.Warnf(`Project uses a non-multistage Dockerfile 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.`,
filepath.Join(scaffold.BuildDir, msDockerfile),
dockerfile)

} else {
absProjectPath := projutil.MustGetwd()
cfg := &input.Config{
AbsProjectPath: absProjectPath,
ProjectName: filepath.Base(absProjectPath),
Repo: projutil.CheckAndGetProjectGoPkg(),
}

dockerfile = filepath.Join(scaffold.BuildDir, msDockerfile)
d := &scaffold.Dockerfile{
Multistage: true,
Input: input.Input{
Path: dockerfile,
IfExistsAction: input.Overwrite,
},
}
err := (&scaffold.Scaffold{}).Execute(cfg, d)
if err != nil {
return "", fmt.Errorf("failed to write %s: (%v)", msDockerfile, err)
}
}
}

return dockerfile, nil
}

// buildOperatorBinary builds the operator binary locally.
func buildOperatorBinary() error {
absProjectPath := projutil.MustGetwd()
binName := filepath.Base(absProjectPath)
goFlags := []string{"build",
"-gcflags", "all=-trimpath=${GOPATH}",
"-asmflags", "all=-trimpath=${GOPATH}",
"-o", filepath.Join(absProjectPath, scaffold.BuildBinDir, binName),
filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir),
}

cmd := exec.Command("go", goFlags...)
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64", "CGO_ENABLED=0")
return projutil.ExecCmd(cmd)
}
2 changes: 1 addition & 1 deletion cmd/operator-sdk/new/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func doScaffold() error {
s := &scaffold.Scaffold{}
err := s.Execute(cfg,
&scaffold.Cmd{},
&scaffold.Dockerfile{},
&scaffold.Dockerfile{Multistage: projutil.IsDockerMultistage()},
&scaffold.Entrypoint{},
&scaffold.UserSetup{},
&scaffold.ServiceAccount{},
Expand Down
Loading