@@ -17,26 +17,29 @@ limitations under the License.
17
17
package controllers
18
18
19
19
import (
20
- "context"
20
+ "archive/tar"
21
+ "bufio"
22
+ "compress/gzip"
21
23
"crypto/sha1"
22
24
"fmt"
25
+ "io"
23
26
"io/ioutil"
24
27
"os"
25
- "os/exec"
26
28
"path/filepath"
27
29
"strings"
28
30
"time"
29
31
32
+ "github.com/go-git/go-git/v5/plumbing/format/gitignore"
30
33
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31
34
32
35
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
33
36
"github.com/fluxcd/source-controller/internal/lockedfile"
34
37
)
35
38
36
39
const (
37
- excludeFile = ".sourceignore"
38
- excludeVCS = ".git/,.gitignore,.gitmodules,.gitattributes"
39
- defaultExcludes = "jpg,jpeg,gif,png,wmv,flv,tar.gz,zip"
40
+ excludeFile = ".sourceignore"
41
+ excludeVCS = ".git/,.gitignore,.gitmodules,.gitattributes"
42
+ excludeExt = "*. jpg,*. jpeg,*. gif,*. png,*. wmv,.* flv,.* tar.gz,*. zip"
40
43
)
41
44
42
45
// Storage manages artifacts
@@ -120,44 +123,74 @@ func (s *Storage) ArtifactExist(artifact sourcev1.Artifact) bool {
120
123
121
124
// Archive creates a tar.gz to the artifact path from the given dir excluding any VCS specific
122
125
// files and directories, or any of the excludes defined in the excludeFiles.
123
- func (s * Storage ) Archive (artifact sourcev1.Artifact , dir string , integrityCheck bool ) error {
124
- ctx , cancel := context .WithTimeout (context .Background (), s .Timeout )
125
- defer cancel ()
126
-
127
- var tarExcludes []string
128
- if _ , err := os .Stat (filepath .Join (dir , excludeFile )); ! os .IsNotExist (err ) {
129
- tarExcludes = append (tarExcludes , "--exclude-file=" + excludeFile )
130
- } else {
131
- tarExcludes = append (tarExcludes , fmt .Sprintf ("--exclude=\\ *.{%s}" , defaultExcludes ))
126
+ func (s * Storage ) Archive (artifact sourcev1.Artifact , dir string ) error {
127
+ if _ , err := os .Stat (dir ); err != nil {
128
+ return err
132
129
}
133
- for _ , excl := range strings .Split (excludeVCS , "," ) {
134
- tarExcludes = append (tarExcludes , "--exclude=" + excl )
130
+
131
+ ps , err := loadExcludePatterns (dir )
132
+ if err != nil {
133
+ return err
135
134
}
136
- cmd := fmt .Sprintf ("cd %s && tar -c %s -f - . | gzip > %s" , dir , strings .Join (tarExcludes , " " ), artifact .Path )
137
- command := exec .CommandContext (ctx , "/bin/sh" , "-c" , cmd )
135
+ matcher := gitignore .NewMatcher (ps )
138
136
139
- err := command . Run ( )
137
+ gzFile , err := os . Create ( artifact . Path )
140
138
if err != nil {
141
- return fmt . Errorf ( "command '%s' failed: %w" , cmd , err )
139
+ return err
142
140
}
141
+ defer gzFile .Close ()
142
+
143
+ gw := gzip .NewWriter (gzFile )
144
+ defer gw .Close ()
145
+
146
+ tw := tar .NewWriter (gw )
147
+ defer tw .Close ()
143
148
144
- if integrityCheck {
145
- cmd = fmt .Sprintf ("gunzip -t %s" , artifact .Path )
146
- command = exec .CommandContext (ctx , "/bin/sh" , "-c" , cmd )
147
- err = command .Run ()
149
+ return filepath .Walk (dir , func (p string , fi os.FileInfo , err error ) error {
148
150
if err != nil {
149
- return fmt .Errorf ("gzip integrity check failed" )
151
+ return err
152
+ }
153
+
154
+ // Ignore anything that is not a file (directories, symlinks)
155
+ if ! fi .Mode ().IsRegular () {
156
+ return nil
150
157
}
151
158
152
- cmd = fmt .Sprintf ("tar -tzf %s >/dev/null" , artifact .Path )
153
- command = exec .CommandContext (ctx , "/bin/sh" , "-c" , cmd )
154
- err = command .Run ()
159
+ // Ignore excluded extensions and files
160
+ if matcher .Match (strings .Split (p , "/" ), false ) {
161
+ return nil
162
+ }
163
+
164
+ header , err := tar .FileInfoHeader (fi , p )
155
165
if err != nil {
156
- return fmt . Errorf ( "tar integrity check failed" )
166
+ return err
157
167
}
158
- }
168
+ // The name needs to be modified to maintain directory structure
169
+ // as tar.FileInfoHeader only has access to the base name of the file.
170
+ // Ref: https://golang.org/src/archive/tar/common.go?#L626
171
+ relFilePath := p
172
+ if filepath .IsAbs (dir ) {
173
+ relFilePath , err = filepath .Rel (dir , p )
174
+ if err != nil {
175
+ return err
176
+ }
177
+ }
178
+ header .Name = relFilePath
159
179
160
- return nil
180
+ if err := tw .WriteHeader (header ); err != nil {
181
+ return err
182
+ }
183
+
184
+ f , err := os .Open (p )
185
+ if err != nil {
186
+ return err
187
+ }
188
+ if _ , err := io .Copy (tw , f ); err != nil {
189
+ f .Close ()
190
+ return err
191
+ }
192
+ return f .Close ()
193
+ })
161
194
}
162
195
163
196
// WriteFile writes the given bytes to the artifact path if the checksum differs
@@ -207,3 +240,28 @@ func (s *Storage) Lock(artifact sourcev1.Artifact) (unlock func(), err error) {
207
240
mutex := lockedfile .MutexAt (lockFile )
208
241
return mutex .Lock ()
209
242
}
243
+
244
+ func loadExcludePatterns (dir string ) ([]gitignore.Pattern , error ) {
245
+ path := strings .Split (dir , "/" )
246
+ var ps []gitignore.Pattern
247
+ for _ , p := range strings .Split (excludeVCS , "," ) {
248
+ ps = append (ps , gitignore .ParsePattern (p , path ))
249
+ }
250
+ for _ , p := range strings .Split (excludeExt , "," ) {
251
+ ps = append (ps , gitignore .ParsePattern (p , path ))
252
+ }
253
+ if f , err := os .Open (filepath .Join (dir , excludeFile )); err == nil {
254
+ defer f .Close ()
255
+
256
+ scanner := bufio .NewScanner (f )
257
+ for scanner .Scan () {
258
+ s := scanner .Text ()
259
+ if ! strings .HasPrefix (s , "#" ) && len (strings .TrimSpace (s )) > 0 {
260
+ ps = append (ps , gitignore .ParsePattern (s , path ))
261
+ }
262
+ }
263
+ } else if ! os .IsNotExist (err ) {
264
+ return nil , err
265
+ }
266
+ return ps , nil
267
+ }
0 commit comments