11package s2i
22
33import (
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.
6644type 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
7351type 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