Skip to content

Commit c30b940

Browse files
authored
Merge pull request #417 from fluxcd/cherry-pick-fixes
2 parents edb4595 + 3ac39b6 commit c30b940

File tree

5 files changed

+96
-51
lines changed

5 files changed

+96
-51
lines changed

controllers/storage.go

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"strings"
3030
"time"
3131

32+
securejoin "github.com/cyphar/filepath-securejoin"
3233
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3435

@@ -83,8 +84,7 @@ func (s Storage) SetArtifactURL(artifact *sourcev1.Artifact) {
8384
artifact.URL = fmt.Sprintf("http://%s/%s", s.Hostname, artifact.Path)
8485
}
8586

86-
// SetHostname sets the hostname of the given URL string to the current Storage.Hostname
87-
// and returns the result.
87+
// SetHostname sets the hostname of the given URL string to the current Storage.Hostname and returns the result.
8888
func (s Storage) SetHostname(URL string) string {
8989
u, err := url.Parse(URL)
9090
if err != nil {
@@ -106,8 +106,7 @@ func (s *Storage) RemoveAll(artifact sourcev1.Artifact) error {
106106
return os.RemoveAll(dir)
107107
}
108108

109-
// RemoveAllButCurrent removes all files for the given v1beta1.Artifact base dir,
110-
// excluding the current one.
109+
// RemoveAllButCurrent removes all files for the given v1beta1.Artifact base dir, excluding the current one.
111110
func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) error {
112111
localPath := s.LocalPath(artifact)
113112
dir := filepath.Dir(localPath)
@@ -132,8 +131,7 @@ func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) error {
132131
return nil
133132
}
134133

135-
// ArtifactExist returns a boolean indicating whether the v1beta1.Artifact exists in storage
136-
// and is a regular file.
134+
// ArtifactExist returns a boolean indicating whether the v1beta1.Artifact exists in storage and is a regular file.
137135
func (s *Storage) ArtifactExist(artifact sourcev1.Artifact) bool {
138136
fi, err := os.Lstat(s.LocalPath(artifact))
139137
if err != nil {
@@ -142,14 +140,13 @@ func (s *Storage) ArtifactExist(artifact sourcev1.Artifact) bool {
142140
return fi.Mode().IsRegular()
143141
}
144142

145-
// ArchiveFileFilter must return true if a file should not be included
146-
// in the archive after inspecting the given path and/or os.FileInfo.
143+
// ArchiveFileFilter must return true if a file should not be included in the archive after inspecting the given path
144+
// and/or os.FileInfo.
147145
type ArchiveFileFilter func(p string, fi os.FileInfo) bool
148146

149-
// SourceIgnoreFilter returns an ArchiveFileFilter that filters out
150-
// files matching sourceignore.VCSPatterns and any of the provided
151-
// patterns. If an empty gitignore.Pattern slice is given, the matcher
152-
// is set to sourceignore.NewDefaultMatcher.
147+
// SourceIgnoreFilter returns an ArchiveFileFilter that filters out files matching sourceignore.VCSPatterns and any of
148+
// the provided patterns.
149+
// If an empty gitignore.Pattern slice is given, the matcher is set to sourceignore.NewDefaultMatcher.
153150
func SourceIgnoreFilter(ps []gitignore.Pattern, domain []string) ArchiveFileFilter {
154151
matcher := sourceignore.NewDefaultMatcher(ps, domain)
155152
if len(ps) > 0 {
@@ -163,10 +160,10 @@ func SourceIgnoreFilter(ps []gitignore.Pattern, domain []string) ArchiveFileFilt
163160
}
164161
}
165162

166-
// Archive atomically archives the given directory as a tarball to the
167-
// given v1beta1.Artifact path, excluding directories and any
168-
// ArchiveFileFilter matches. If successful, it sets the checksum and
169-
// last update time on the artifact.
163+
// Archive atomically archives the given directory as a tarball to the given v1beta1.Artifact path, excluding
164+
// directories and any ArchiveFileFilter matches. While archiving, any environment specific data (for example,
165+
// the user and group name) is stripped from file headers.
166+
// If successful, it sets the checksum and last update time on the artifact.
170167
func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter ArchiveFileFilter) (err error) {
171168
if f, err := os.Stat(dir); os.IsNotExist(err) || !f.IsDir() {
172169
return fmt.Errorf("invalid dir path: %s", dir)
@@ -220,6 +217,16 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv
220217
}
221218
header.Name = relFilePath
222219

220+
// We want to remove any environment specific data as well, this
221+
// ensures the checksum is purely content based.
222+
header.Gid = 0
223+
header.Uid = 0
224+
header.Uname = ""
225+
header.Gname = ""
226+
header.ModTime = time.Time{}
227+
header.AccessTime = time.Time{}
228+
header.ChangeTime = time.Time{}
229+
223230
if err := tw.WriteHeader(header); err != nil {
224231
return err
225232
}
@@ -341,9 +348,8 @@ func (s *Storage) Copy(artifact *sourcev1.Artifact, reader io.Reader) (err error
341348
return nil
342349
}
343350

344-
// CopyFromPath atomically copies the contents of the given path to the path of
345-
// the v1beta1.Artifact. If successful, the checksum and last update time on the
346-
// artifact is set.
351+
// CopyFromPath atomically copies the contents of the given path to the path of the v1beta1.Artifact.
352+
// If successful, the checksum and last update time on the artifact is set.
347353
func (s *Storage) CopyFromPath(artifact *sourcev1.Artifact, path string) (err error) {
348354
f, err := os.Open(path)
349355
if err != nil {
@@ -353,10 +359,10 @@ func (s *Storage) CopyFromPath(artifact *sourcev1.Artifact, path string) (err er
353359
return s.Copy(artifact, f)
354360
}
355361

356-
// CopyToPath copies the contents of the given artifact to the path.
362+
// CopyToPath copies the contents in the (sub)path of the given artifact to the given path.
357363
func (s *Storage) CopyToPath(artifact *sourcev1.Artifact, subPath, toPath string) error {
358364
// create a tmp directory to store artifact
359-
tmp, err := os.MkdirTemp("", "flux-include")
365+
tmp, err := os.MkdirTemp("", "flux-include-")
360366
if err != nil {
361367
return err
362368
}
@@ -371,7 +377,7 @@ func (s *Storage) CopyToPath(artifact *sourcev1.Artifact, subPath, toPath string
371377
defer f.Close()
372378

373379
// untar the artifact
374-
untarPath := filepath.Join(tmp, "tar")
380+
untarPath := filepath.Join(tmp, "unpack")
375381
if _, err = untar.Untar(f, untarPath); err != nil {
376382
return err
377383
}
@@ -382,15 +388,17 @@ func (s *Storage) CopyToPath(artifact *sourcev1.Artifact, subPath, toPath string
382388
}
383389

384390
// copy the artifact content to the destination dir
385-
fromPath := filepath.Join(untarPath, subPath)
391+
fromPath, err := securejoin.SecureJoin(untarPath, subPath)
392+
if err != nil {
393+
return err
394+
}
386395
if err := fs.RenameWithFallback(fromPath, toPath); err != nil {
387396
return err
388397
}
389398
return nil
390399
}
391400

392-
// Symlink creates or updates a symbolic link for the given v1beta1.Artifact
393-
// and returns the URL for the symlink.
401+
// Symlink creates or updates a symbolic link for the given v1beta1.Artifact and returns the URL for the symlink.
394402
func (s *Storage) Symlink(artifact sourcev1.Artifact, linkName string) (string, error) {
395403
localPath := s.LocalPath(artifact)
396404
dir := filepath.Dir(localPath)
@@ -427,13 +435,16 @@ func (s *Storage) Lock(artifact sourcev1.Artifact) (unlock func(), err error) {
427435
return mutex.Lock()
428436
}
429437

430-
// LocalPath returns the local path of the given artifact (that is: relative to
431-
// the Storage.BasePath).
438+
// LocalPath returns the secure local path of the given artifact (that is: relative to the Storage.BasePath).
432439
func (s *Storage) LocalPath(artifact sourcev1.Artifact) string {
433440
if artifact.Path == "" {
434441
return ""
435442
}
436-
return filepath.Join(s.BasePath, artifact.Path)
443+
path, err := securejoin.SecureJoin(s.BasePath, artifact.Path)
444+
if err != nil {
445+
return ""
446+
}
447+
return path
437448
}
438449

439450
// newHash returns a new SHA1 hash.

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ replace github.com/fluxcd/source-controller/api => ./api
66

77
require (
88
github.com/Masterminds/semver/v3 v3.1.1
9-
github.com/blang/semver/v4 v4.0.0
109
github.com/cyphar/filepath-securejoin v0.2.2
1110
github.com/fluxcd/pkg/apis/meta v0.10.0
1211
github.com/fluxcd/pkg/gittestserver v0.3.0

go.sum

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
113113
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
114114
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
115115
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
116-
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
117116
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
118-
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
119-
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
120117
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
121118
github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA=
122119
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=

pkg/git/gogit/checkout.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
193193

194194
tags := make(map[string]string)
195195
tagTimestamps := make(map[string]time.Time)
196-
_ = repoTags.ForEach(func(t *plumbing.Reference) error {
196+
if err = repoTags.ForEach(func(t *plumbing.Reference) error {
197197
revision := plumbing.Revision(t.Name().String())
198198
hash, err := repo.ResolveRevision(revision)
199199
if err != nil {
@@ -207,7 +207,9 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
207207

208208
tags[t.Name().Short()] = t.Strings()[1]
209209
return nil
210-
})
210+
}); err != nil {
211+
return nil, "", err
212+
}
211213

212214
var matchedVersions semver.Collection
213215
for tag, _ := range tags {

pkg/git/libgit2/checkout.go

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ package libgit2
1919
import (
2020
"context"
2121
"fmt"
22+
"sort"
23+
"time"
2224

23-
"github.com/blang/semver/v4"
25+
"github.com/Masterminds/semver/v3"
26+
"github.com/fluxcd/pkg/version"
2427
git2go "github.com/libgit2/git2go/v31"
2528

2629
"github.com/fluxcd/pkg/gitutil"
@@ -168,7 +171,7 @@ type CheckoutSemVer struct {
168171
}
169172

170173
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *git.Auth) (git.Commit, string, error) {
171-
rng, err := semver.ParseRange(c.semVer)
174+
verConstraint, err := semver.NewConstraint(c.semVer)
172175
if err != nil {
173176
return nil, "", fmt.Errorf("semver parse range error: %w", err)
174177
}
@@ -186,28 +189,61 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *g
186189
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
187190
}
188191

189-
repoTags, err := repo.Tags.List()
190-
if err != nil {
191-
return nil, "", fmt.Errorf("git list tags error: %w", err)
192-
}
192+
tags := make(map[string]string)
193+
tagTimestamps := make(map[string]time.Time)
194+
if err := repo.Tags.Foreach(func(name string, id *git2go.Oid) error {
195+
tag, err := repo.LookupTag(id)
196+
if err != nil {
197+
return nil
198+
}
193199

194-
svTags := make(map[string]string)
195-
var svers []semver.Version
196-
for _, tag := range repoTags {
197-
v, _ := semver.ParseTolerant(tag)
198-
if rng(v) {
199-
svers = append(svers, v)
200-
svTags[v.String()] = tag
200+
commit, err := tag.Peel(git2go.ObjectCommit)
201+
if err != nil {
202+
return fmt.Errorf("can't get commit for tag %s: %w", name, err)
201203
}
204+
c, err := commit.AsCommit()
205+
if err != nil {
206+
return err
207+
}
208+
tagTimestamps[tag.Name()] = c.Committer().When
209+
tags[tag.Name()] = name
210+
return nil
211+
}); err != nil {
212+
return nil, "", err
202213
}
203214

204-
if len(svers) == 0 {
215+
var matchedVersions semver.Collection
216+
for tag, _ := range tags {
217+
v, err := version.ParseVersion(tag)
218+
if err != nil {
219+
continue
220+
}
221+
if !verConstraint.Check(v) {
222+
continue
223+
}
224+
matchedVersions = append(matchedVersions, v)
225+
}
226+
if len(matchedVersions) == 0 {
205227
return nil, "", fmt.Errorf("no match found for semver: %s", c.semVer)
206228
}
207229

208-
semver.Sort(svers)
209-
v := svers[len(svers)-1]
210-
t := svTags[v.String()]
230+
// Sort versions
231+
sort.SliceStable(matchedVersions, func(i, j int) bool {
232+
left := matchedVersions[i]
233+
right := matchedVersions[j]
234+
235+
if !left.Equal(right) {
236+
return left.LessThan(right)
237+
}
238+
239+
// Having tag target timestamps at our disposal, we further try to sort
240+
// versions into a chronological order. This is especially important for
241+
// versions that differ only by build metadata, because it is not considered
242+
// a part of the comparable version in Semver
243+
return tagTimestamps[left.String()].Before(tagTimestamps[right.String()])
244+
})
245+
v := matchedVersions[len(matchedVersions)-1]
246+
t := v.Original()
211247

212248
ref, err := repo.References.Dwim(t)
213249
if err != nil {

0 commit comments

Comments
 (0)