-
Notifications
You must be signed in to change notification settings - Fork 695
/
onbuild.go
195 lines (173 loc) · 6.45 KB
/
onbuild.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package onbuild
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/api/constants"
"github.com/openshift/source-to-image/pkg/build"
"github.com/openshift/source-to-image/pkg/build/strategies/sti"
"github.com/openshift/source-to-image/pkg/docker"
"github.com/openshift/source-to-image/pkg/ignore"
"github.com/openshift/source-to-image/pkg/scm"
"github.com/openshift/source-to-image/pkg/scm/git"
"github.com/openshift/source-to-image/pkg/scripts"
"github.com/openshift/source-to-image/pkg/tar"
"github.com/openshift/source-to-image/pkg/util/cmd"
"github.com/openshift/source-to-image/pkg/util/fs"
utilstatus "github.com/openshift/source-to-image/pkg/util/status"
)
// OnBuild strategy executes the simple Docker build in case the image does not
// support STI scripts but has ONBUILD instructions recorded.
type OnBuild struct {
docker docker.Docker
git git.Git
fs fs.FileSystem
tar tar.Tar
source build.SourceHandler
garbage build.Cleaner
}
type onBuildSourceHandler struct {
build.Downloader
build.Preparer
build.Ignorer
}
// New returns a new instance of OnBuild builder
func New(client docker.Client, config *api.Config, fs fs.FileSystem, overrides build.Overrides) (*OnBuild, error) {
dockerHandler := docker.New(client, config.PullAuthentication)
builder := &OnBuild{
docker: dockerHandler,
git: git.New(fs, cmd.NewCommandRunner()),
fs: fs,
tar: tar.New(fs),
}
// Use STI Prepare() and download the 'run' script optionally.
s, err := sti.New(client, config, fs, overrides)
if err != nil {
return nil, err
}
s.SetScripts([]string{}, []string{constants.Assemble, constants.Run})
downloader := overrides.Downloader
if downloader == nil {
downloader, err = scm.DownloaderForSource(builder.fs, config.Source, config.ForceCopy)
if err != nil {
return nil, err
}
}
builder.source = onBuildSourceHandler{
Downloader: downloader,
Preparer: s,
Ignorer: &ignore.DockerIgnorer{},
}
builder.garbage = build.NewDefaultCleaner(builder.fs, builder.docker)
return builder, nil
}
// Build executes the ONBUILD kind of build
func (builder *OnBuild) Build(config *api.Config) (*api.Result, error) {
buildResult := &api.Result{}
if config.BlockOnBuild {
buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
utilstatus.ReasonOnBuildForbidden,
utilstatus.ReasonMessageOnBuildForbidden,
)
return buildResult, fmt.Errorf("builder image uses ONBUILD instructions but ONBUILD is not allowed")
}
log.V(2).Info("Preparing the source code for build")
// Change the installation directory for this config to store scripts inside
// the application root directory.
if err := builder.source.Prepare(config); err != nil {
return buildResult, err
}
// If necessary, copy the STI scripts into application root directory
builder.copySTIScripts(config)
log.V(2).Info("Creating application Dockerfile")
if err := builder.CreateDockerfile(config); err != nil {
buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
utilstatus.ReasonDockerfileCreateFailed,
utilstatus.ReasonMessageDockerfileCreateFailed,
)
return buildResult, err
}
log.V(2).Info("Creating application source code image")
tarStream := builder.tar.CreateTarStreamReader(filepath.Join(config.WorkingDir, "upload", "src"), false)
defer tarStream.Close()
outReader, outWriter := io.Pipe()
go io.Copy(os.Stdout, outReader)
opts := docker.BuildImageOptions{
Name: config.Tag,
Stdin: tarStream,
Stdout: outWriter,
CGroupLimits: config.CGroupLimits,
}
log.V(2).Info("Building the application source")
if err := builder.docker.BuildImage(opts); err != nil {
buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
utilstatus.ReasonDockerImageBuildFailed,
utilstatus.ReasonMessageDockerImageBuildFailed,
)
return buildResult, err
}
log.V(2).Info("Cleaning up temporary containers")
builder.garbage.Cleanup(config)
var imageID string
var err error
if len(opts.Name) > 0 {
if imageID, err = builder.docker.GetImageID(opts.Name); err != nil {
buildResult.BuildInfo.FailureReason = utilstatus.NewFailureReason(
utilstatus.ReasonGenericS2IBuildFailed,
utilstatus.ReasonMessageGenericS2iBuildFailed,
)
return buildResult, err
}
}
return &api.Result{
Success: true,
WorkingDir: config.WorkingDir,
ImageID: imageID,
}, nil
}
// CreateDockerfile creates the ONBUILD Dockerfile
func (builder *OnBuild) CreateDockerfile(config *api.Config) error {
buffer := bytes.Buffer{}
uploadDir := filepath.Join(config.WorkingDir, "upload", "src")
buffer.WriteString(fmt.Sprintf("FROM %s\n", config.BuilderImage))
entrypoint, err := GuessEntrypoint(builder.fs, uploadDir)
if err != nil {
return err
}
env, err := scripts.GetEnvironment(filepath.Join(config.WorkingDir, constants.Source))
if err != nil {
log.V(1).Infof("Environment: %v", err)
} else {
buffer.WriteString(scripts.ConvertEnvironmentToDocker(env))
}
// If there is an assemble script present, run it as part of the build process
// as the last thing.
if builder.hasAssembleScript(config) {
buffer.WriteString("RUN sh assemble\n")
}
// FIXME: This assumes that the WORKDIR is set to the application source root
// directory.
buffer.WriteString(fmt.Sprintf(`ENTRYPOINT ["./%s"]`+"\n", entrypoint))
return builder.fs.WriteFile(filepath.Join(uploadDir, "Dockerfile"), buffer.Bytes())
}
func (builder *OnBuild) copySTIScripts(config *api.Config) {
scriptsPath := filepath.Join(config.WorkingDir, "upload", "scripts")
sourcePath := filepath.Join(config.WorkingDir, "upload", "src")
if _, err := builder.fs.Stat(filepath.Join(scriptsPath, constants.Run)); err == nil {
log.V(3).Info("Found S2I 'run' script, copying to application source dir")
builder.fs.Copy(filepath.Join(scriptsPath, constants.Run), filepath.Join(sourcePath, constants.Run), nil)
}
if _, err := builder.fs.Stat(filepath.Join(scriptsPath, constants.Assemble)); err == nil {
log.V(3).Info("Found S2I 'assemble' script, copying to application source dir")
builder.fs.Copy(filepath.Join(scriptsPath, constants.Assemble), filepath.Join(sourcePath, constants.Assemble), nil)
}
}
// hasAssembleScript checks if the the assemble script is available
func (builder *OnBuild) hasAssembleScript(config *api.Config) bool {
assemblePath := filepath.Join(config.WorkingDir, "upload", "src", constants.Assemble)
_, err := builder.fs.Stat(assemblePath)
return err == nil
}