Skip to content

Commit 3680316

Browse files
authored
Use s2i in standard mode, not "as-dockerfile" (knative#2764)
Signed-off-by: Matej Vašek <mvasek@redhat.com>
1 parent b6bfb2d commit 3680316

2 files changed

Lines changed: 62 additions & 490 deletions

File tree

pkg/builders/s2i/builder.go

Lines changed: 4 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
11
package s2i
22

33
import (
4-
"archive/tar"
54
"context"
6-
"crypto/sha1"
7-
"encoding/hex"
85
"errors"
96
"fmt"
10-
"io"
11-
"io/fs"
12-
"maps"
137
"net/url"
148
"os"
159
"path/filepath"
16-
"regexp"
17-
"runtime"
18-
"slices"
1910
"strings"
2011

21-
"github.com/docker/docker/api/types"
2212
dockerClient "github.com/docker/docker/client"
23-
"github.com/docker/docker/pkg/jsonmessage"
24-
"github.com/google/go-containerregistry/pkg/name"
25-
v1 "github.com/google/go-containerregistry/pkg/v1"
26-
"github.com/google/go-containerregistry/pkg/v1/remote"
2713
"github.com/openshift/source-to-image/pkg/api"
2814
"github.com/openshift/source-to-image/pkg/api/validation"
2915
"github.com/openshift/source-to-image/pkg/build"
3016
"github.com/openshift/source-to-image/pkg/build/strategies"
3117
s2idocker "github.com/openshift/source-to-image/pkg/docker"
3218
"github.com/openshift/source-to-image/pkg/scm/git"
33-
"golang.org/x/term"
34-
3519
"knative.dev/func/pkg/builders"
3620
"knative.dev/func/pkg/docker"
3721
fn "knative.dev/func/pkg/functions"
@@ -56,18 +40,12 @@ var DefaultBuilderImages = map[string]string{
5640
"typescript": DefaultNodeBuilder,
5741
}
5842

59-
// DockerClient is subset of dockerClient.CommonAPIClient required by this package
60-
type DockerClient interface {
61-
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
62-
ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, error)
63-
}
64-
6543
// Builder of functions using the s2i subsystem.
6644
type Builder struct {
6745
name string
6846
verbose bool
6947
impl build.Builder // S2I builder implementation (aka "Strategy")
70-
cli DockerClient
48+
cli s2idocker.Client
7149
}
7250

7351
type Option func(*Builder)
@@ -94,7 +72,7 @@ func WithImpl(s build.Builder) Option {
9472
}
9573
}
9674

97-
func WithDockerClient(cli DockerClient) Option {
75+
func WithDockerClient(cli s2idocker.Client) Option {
9876
return func(b *Builder) {
9977
b.cli = cli
10078
}
@@ -165,13 +143,6 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
165143
}
166144
}
167145

168-
// Build directory
169-
tmp, err := os.MkdirTemp("", "func-s2i-build")
170-
if err != nil {
171-
return fmt.Errorf("cannot create temporary dir for s2i build: %w", err)
172-
}
173-
defer os.RemoveAll(tmp)
174-
175146
// Build Config
176147
cfg := &api.Config{
177148
Source: &git.URL{
@@ -185,27 +156,13 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
185156
PreviousImagePullPolicy: api.DefaultPreviousImagePullPolicy,
186157
RuntimeImagePullPolicy: api.DefaultRuntimeImagePullPolicy,
187158
DockerConfig: s2idocker.GetDefaultDockerConfig(),
188-
AsDockerfile: filepath.Join(tmp, "Dockerfile"),
189159
}
190160

191161
// Scaffold
192162
if cfg, err = scaffold(cfg, f); err != nil {
193163
return
194164
}
195165

196-
// Extract a an S2I script url from the image if provided and use
197-
// this in the build config.
198-
scriptURL, err := s2iScriptURL(ctx, client, cfg.BuilderImage)
199-
if err != nil {
200-
return fmt.Errorf("cannot get s2i script url: %w", err)
201-
} else if scriptURL != "image:///usr/libexec/s2i" {
202-
// Only set if the label found on the image is NOT the default.
203-
// Otherwise this label, which is essentially a default fallback, will
204-
// take precidence over any scripts provided in ./.s2i/bin, which are
205-
// supposed to be the override to that default.
206-
cfg.ScriptsURL = scriptURL
207-
}
208-
209166
// Excludes
210167
// Do not include .git, .env, .func or any language-specific cache directories
211168
// (node_modules, etc) in the tar file sent to the builder, as this both
@@ -235,7 +192,7 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
235192
// Create the S2I builder instance if not overridden
236193
var impl = b.impl
237194
if impl == nil {
238-
impl, _, err = strategies.Strategy(nil, cfg, build.Overrides{})
195+
impl, _, err = strategies.Strategy(client, cfg, build.Overrides{})
239196
if err != nil {
240197
return fmt.Errorf("cannot create s2i builder: %w", err)
241198
}
@@ -252,184 +209,7 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
252209
fmt.Fprintln(os.Stderr, message)
253210
}
254211
}
255-
256-
pr, pw := io.Pipe()
257-
258-
// s2i apparently is not excluding the files in --as-dockerfile mode
259-
exclude := regexp.MustCompile(cfg.ExcludeRegExp)
260-
261-
// if exists, patch dockerfile to using cache mount
262-
if _, e := os.Stat(cfg.AsDockerfile); e == nil {
263-
err = patchDockerfile(cfg.AsDockerfile, f)
264-
if err != nil {
265-
return err
266-
}
267-
}
268-
269-
const up = ".." + string(os.PathSeparator)
270-
go func() {
271-
tw := tar.NewWriter(pw)
272-
err := filepath.Walk(tmp, func(path string, fi fs.FileInfo, err error) error {
273-
if err != nil {
274-
return err
275-
}
276-
277-
p, err := filepath.Rel(tmp, path)
278-
if err != nil {
279-
return fmt.Errorf("cannot get relative path: %w", err)
280-
}
281-
if p == "." {
282-
return nil
283-
}
284-
285-
p = filepath.ToSlash(p)
286-
287-
if exclude.MatchString(p) {
288-
return nil
289-
}
290-
291-
lnk := ""
292-
if fi.Mode()&fs.ModeSymlink != 0 {
293-
lnk, err = os.Readlink(path)
294-
if err != nil {
295-
return fmt.Errorf("cannot read link: %w", err)
296-
}
297-
if filepath.IsAbs(lnk) {
298-
lnk, err = filepath.Rel(tmp, lnk)
299-
if err != nil {
300-
return fmt.Errorf("cannot get relative path for symlink: %w", err)
301-
}
302-
if strings.HasPrefix(lnk, up) || lnk == ".." {
303-
return fmt.Errorf("link %q points outside source root", p)
304-
}
305-
}
306-
}
307-
308-
hdr, err := tar.FileInfoHeader(fi, filepath.ToSlash(lnk))
309-
if err != nil {
310-
return fmt.Errorf("cannot create tar header: %w", err)
311-
}
312-
hdr.Name = p
313-
314-
if runtime.GOOS == "windows" {
315-
// Windows does not have execute permission, we assume that all files are executable.
316-
hdr.Mode |= 0111
317-
}
318-
319-
err = tw.WriteHeader(hdr)
320-
if err != nil {
321-
return fmt.Errorf("cannot write header to thar stream: %w", err)
322-
}
323-
if fi.Mode().IsRegular() {
324-
var r io.ReadCloser
325-
r, err = os.Open(path)
326-
if err != nil {
327-
return fmt.Errorf("cannot open source file: %w", err)
328-
}
329-
defer r.Close()
330-
331-
_, err = io.Copy(tw, r)
332-
if err != nil {
333-
return fmt.Errorf("cannot copy file to tar stream :%w", err)
334-
}
335-
}
336-
337-
return nil
338-
})
339-
_ = tw.Close()
340-
_ = pw.CloseWithError(err)
341-
}()
342-
343-
opts := types.ImageBuildOptions{
344-
Tags: []string{f.Build.Image},
345-
PullParent: true,
346-
Version: types.BuilderBuildKit,
347-
}
348-
349-
resp, err := client.ImageBuild(ctx, pr, opts)
350-
if err != nil {
351-
return fmt.Errorf("cannot build the app image: %w", err)
352-
}
353-
defer resp.Body.Close()
354-
355-
var out io.Writer = io.Discard
356-
if b.verbose {
357-
out = os.Stderr
358-
}
359-
360-
var isTerminal bool
361-
var fd uintptr
362-
if outF, ok := out.(*os.File); ok {
363-
fd = outF.Fd()
364-
isTerminal = term.IsTerminal(int(outF.Fd()))
365-
}
366-
367-
return jsonmessage.DisplayJSONMessagesStream(resp.Body, out, fd, isTerminal, nil)
368-
}
369-
370-
func patchDockerfile(path string, f fn.Function) error {
371-
data, err := os.ReadFile(path)
372-
if err != nil {
373-
return err
374-
}
375-
re := regexp.MustCompile(`RUN (.*assemble)`)
376-
s := sha1.Sum([]byte(f.Root))
377-
mountCmd := "--mount=type=cache,target=/tmp/artifacts/,uid=1001,id=" + hex.EncodeToString(s[:8])
378-
replacement := fmt.Sprintf("RUN %s \\\n $1", mountCmd)
379-
newDockerFileStr := re.ReplaceAllString(string(data), replacement)
380-
381-
return os.WriteFile(path, []byte(newDockerFileStr), 0644)
382-
}
383-
384-
func s2iScriptURL(ctx context.Context, cli DockerClient, image string) (string, error) {
385-
img, _, err := cli.ImageInspectWithRaw(ctx, image)
386-
if err != nil {
387-
if dockerClient.IsErrNotFound(err) { // image is not in the daemon, get info directly from registry
388-
var (
389-
ref name.Reference
390-
img v1.Image
391-
cfg *v1.ConfigFile
392-
)
393-
394-
ref, err = name.ParseReference(image)
395-
if err != nil {
396-
return "", fmt.Errorf("cannot parse image name: %w", err)
397-
}
398-
if _, ok := ref.(name.Tag); ok && !slices.Contains(slices.Collect(maps.Values(DefaultBuilderImages)), image) {
399-
fmt.Fprintln(os.Stderr, "image referenced by tag which is discouraged: Tags are mutable and can point to a different artifact than the expected one")
400-
}
401-
img, err = remote.Image(ref)
402-
if err != nil {
403-
return "", fmt.Errorf("cannot get image from registry: %w", err)
404-
}
405-
cfg, err = img.ConfigFile()
406-
if err != nil {
407-
return "", fmt.Errorf("cannot get config for image: %w", err)
408-
}
409-
410-
if cfg.Config.Labels != nil {
411-
if u, ok := cfg.Config.Labels["io.openshift.s2i.scripts-url"]; ok {
412-
return u, nil
413-
}
414-
}
415-
}
416-
return "", err
417-
}
418-
419-
if img.Config != nil && img.Config.Labels != nil {
420-
if u, ok := img.Config.Labels["io.openshift.s2i.scripts-url"]; ok {
421-
return u, nil
422-
}
423-
}
424-
425-
//nolint:staticcheck
426-
if img.ContainerConfig != nil && img.ContainerConfig.Labels != nil {
427-
if u, ok := img.ContainerConfig.Labels["io.openshift.s2i.scripts-url"]; ok {
428-
return u, nil
429-
}
430-
}
431-
432-
return "", nil
212+
return nil
433213
}
434214

435215
// Builder Image chooses the correct builder image or defaults.

0 commit comments

Comments
 (0)