@@ -17,17 +17,15 @@ limitations under the License.
1717package oci
1818
1919import (
20- "archive/tar"
21- "compress/gzip"
2220 "fmt"
2321 "io"
2422 "os"
2523 "path/filepath"
2624 "strings"
27- "time"
2825
2926 "github.com/fluxcd/pkg/oci/internal/fs"
3027 "github.com/fluxcd/pkg/sourceignore"
28+ "github.com/fluxcd/pkg/tar"
3129)
3230
3331// Build archives the given directory as a tarball to the given local path.
@@ -37,17 +35,20 @@ func (c *Client) Build(artifactPath, sourceDir string, ignorePaths []string) (er
3735}
3836
3937func build (artifactPath , sourceDir string , ignorePaths []string ) (err error ) {
40- absDir , err := filepath .Abs (sourceDir )
38+ absSrc , err := filepath .Abs (sourceDir )
4139 if err != nil {
4240 return err
4341 }
4442
45- dirStat , err := os .Stat (absDir )
46- if os .IsNotExist (err ) {
47- return fmt .Errorf ("invalid source dir path: %s" , absDir )
43+ srcInfo , err := os .Stat (absSrc )
44+ if err != nil {
45+ if os .IsNotExist (err ) {
46+ return fmt .Errorf ("source path does not exist: %s" , absSrc )
47+ }
48+ return fmt .Errorf ("invalid source path %s: %w" , absSrc , err )
4849 }
4950
50- tf , err := os .CreateTemp (filepath .Split (absDir ))
51+ tf , err := os .CreateTemp (filepath .Split (absSrc ))
5152 if err != nil {
5253 return err
5354 }
@@ -58,110 +59,60 @@ func build(artifactPath, sourceDir string, ignorePaths []string) (err error) {
5859 }
5960 }()
6061
61- ignore := strings .Join (ignorePaths , "\n " )
62- domain := strings .Split (filepath .Clean (absDir ), string (filepath .Separator ))
63- ps := sourceignore .ReadPatterns (strings .NewReader (ignore ), domain )
64- matcher := sourceignore .NewMatcher (ps )
65- filter := func (p string , fi os.FileInfo ) bool {
66- return matcher .Match (strings .Split (p , string (filepath .Separator )), fi .IsDir ())
67- }
68-
69- sz := & writeCounter {}
70- mw := io .MultiWriter (tf , sz )
71-
72- gw := gzip .NewWriter (mw )
73- tw := tar .NewWriter (gw )
74- if err := filepath .Walk (absDir , func (p string , fi os.FileInfo , err error ) error {
75- if err != nil {
76- return err
77- }
78-
79- // Ignore anything that is not a file or directories e.g. symlinks
80- if m := fi .Mode (); ! (m .IsRegular () || m .IsDir ()) {
81- return nil
82- }
83-
84- if len (ignorePaths ) > 0 && filter (p , fi ) {
85- return nil
62+ // If the source is a single file, stage it in a temp dir so Tar can
63+ // archive it as a directory tree containing that one entry.
64+ tarDir := absSrc
65+ if ! srcInfo .IsDir () {
66+ stage , stageErr := os .MkdirTemp ("" , "oci-build-" )
67+ if stageErr != nil {
68+ tf .Close ()
69+ return stageErr
8670 }
71+ defer os .RemoveAll (stage )
8772
88- header , err := tar .FileInfoHeader (fi , p )
89- if err != nil {
90- return err
91- }
92- if dirStat .IsDir () {
93- // The name needs to be modified to maintain directory structure
94- // as tar.FileInfoHeader only has access to the base name of the file.
95- // Ref: https://golang.org/src/archive/tar/common.go?#L6264
96- //
97- // we only want to do this if a directory was passed in
98- relFilePath , err := filepath .Rel (absDir , p )
99- if err != nil {
100- return err
101- }
102- // Normalize file path so it works on windows
103- header .Name = filepath .ToSlash (relFilePath )
104- }
105-
106- // Remove any environment specific data.
107- header .Gid = 0
108- header .Uid = 0
109- header .Uname = ""
110- header .Gname = ""
111- header .ModTime = time.Time {}
112- header .AccessTime = time.Time {}
113- header .ChangeTime = time.Time {}
114-
115- if err := tw .WriteHeader (header ); err != nil {
116- return err
117- }
118-
119- if ! fi .Mode ().IsRegular () {
120- return nil
121- }
122- f , err := os .Open (p )
123- if err != nil {
124- f .Close ()
125- return err
126- }
127- if _ , err := io .Copy (tw , f ); err != nil {
128- f .Close ()
73+ if err := copyFileContents (filepath .Join (stage , srcInfo .Name ()), absSrc , srcInfo .Mode ()); err != nil {
74+ tf .Close ()
12975 return err
13076 }
131- return f .Close ()
132- }); err != nil {
133- tw .Close ()
134- gw .Close ()
135- tf .Close ()
136- return err
77+ tarDir = stage
13778 }
13879
139- if err := tw .Close (); err != nil {
140- gw .Close ()
141- tf .Close ()
142- return err
80+ ignore := strings .Join (ignorePaths , "\n " )
81+ domain := strings .Split (filepath .Clean (tarDir ), string (filepath .Separator ))
82+ ps := sourceignore .ReadPatterns (strings .NewReader (ignore ), domain )
83+ matcher := sourceignore .NewMatcher (ps )
84+ filter := func (p string , fi os.FileInfo ) bool {
85+ return matcher .Match (strings .Split (p , string (filepath .Separator )), fi .IsDir ())
14386 }
144- if err := gw .Close (); err != nil {
87+
88+ if _ , err := tar .Tar (tarDir , tf , tar .WithFilter (filter )); err != nil {
14589 tf .Close ()
14690 return err
14791 }
14892 if err := tf .Close (); err != nil {
14993 return err
15094 }
151-
15295 if err := os .Chmod (tmpName , 0o640 ); err != nil {
15396 return err
15497 }
155-
15698 return fs .RenameWithFallback (tmpName , artifactPath )
15799}
158100
159- type writeCounter struct {
160- written int64
161- }
162-
163- func (wc * writeCounter ) Write (p []byte ) (int , error ) {
164- n := len (p )
165- wc .written += int64 (n )
166- return n , nil
101+ func copyFileContents (dst , src string , mode os.FileMode ) (err error ) {
102+ sf , err := os .Open (src )
103+ if err != nil {
104+ return err
105+ }
106+ defer sf .Close ()
107+ df , err := os .OpenFile (dst , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , mode .Perm ())
108+ if err != nil {
109+ return err
110+ }
111+ defer func () {
112+ if closeErr := df .Close (); closeErr != nil && err == nil {
113+ err = closeErr
114+ }
115+ }()
116+ _ , err = io .Copy (df , sf )
117+ return err
167118}
0 commit comments