Skip to content

Commit d064b7a

Browse files
committed
feat: anchor regex and escape meta characters
1 parent 3741c49 commit d064b7a

4 files changed

Lines changed: 91 additions & 14 deletions

File tree

internal/commands/update_stemcell.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,12 @@ func (update UpdateStemcell) Execute(args []string) error {
9090
}
9191

9292
if err != nil {
93+
if component.IsErrNotFound(err) {
94+
return fmt.Errorf("couldn't find release %q", rel.Name)
95+
}
9396
return fmt.Errorf("while finding release %q, encountered error: %w", rel.Name, err)
9497
}
9598

96-
if component.IsErrNotFound(err) {
97-
return fmt.Errorf("couldn't find release %q", rel.Name)
98-
}
99-
10099
if remote.RemotePath == rel.RemotePath && remote.RemoteSource == rel.RemoteSource {
101100
update.Logger.Printf("No change for release %q\n", rel.Name)
102101

internal/commands/update_stemcell_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -615,13 +615,13 @@ var _ = Describe("UpdateStemcell", func() {
615615
releaseSource.GetMatchedReleaseReturns(cargo.BOSHReleaseTarballLock{}, component.ErrNotFound)
616616
})
617617

618-
It("errors", func() {
618+
It("returns a user-friendly not-found message", func() {
619619
err := update.Execute([]string{"--kilnfile", kilnfilePath, "--version", newStemcellVersion})
620620

621-
Expect(err).To(MatchError(And(
622-
ContainSubstring(component.ErrNotFound.Error()),
623-
ContainSubstring(release1Name),
624-
)))
621+
Expect(err).To(HaveOccurred())
622+
Expect(err.Error()).To(ContainSubstring("couldn't find release"))
623+
Expect(err.Error()).To(ContainSubstring(release1Name))
624+
Expect(err.Error()).NotTo(ContainSubstring("while finding release"))
625625
})
626626
})
627627

internal/component/artifactory_release_source.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRe
102102
if err != nil {
103103
return Local{}, err
104104
}
105+
defer resp.Body.Close()
105106

106107
if resp.StatusCode != http.StatusOK {
107108
return Local{}, fmt.Errorf("failed to download %s release from artifactory with error code %d", remoteRelease.Name, resp.StatusCode)
@@ -119,7 +120,6 @@ func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRe
119120

120121
mw := io.MultiWriter(out, hash)
121122
_, err = io.Copy(mw, resp.Body)
122-
_ = resp.Body.Close()
123123
if err != nil {
124124
return Local{}, err
125125
}
@@ -242,17 +242,29 @@ func (ars *ArtifactoryReleaseSource) findReleaseVersion(spec, searchSpec cargo.B
242242
return foundRelease, nil
243243
}
244244

245+
const (
246+
versionPlaceholder = "\x00VERSION_CAPTURE_GROUP\x00"
247+
stemcellVersionPlaceholder = "\x00STEMCELL_VERSION_CAPTURE_GROUP\x00"
248+
)
249+
245250
func (ars *ArtifactoryReleaseSource) regexPatternFromSpec(spec cargo.BOSHReleaseTarballSpecification) (*regexp.Regexp, error) {
251+
versionGroup := fmt.Sprintf(`(?P<%s>(%s))`, reReleaseVersionGroup, semverRegex)
252+
stemcellVersionGroup := fmt.Sprintf(`(?P<%s>(%s))`, reStemcellVersionGroup, semverRegex)
253+
246254
regexSpec := spec
247-
regexSpec.Version = fmt.Sprintf(`(?P<%s>(%s))`, reReleaseVersionGroup, semverRegex)
248-
regexSpec.StemcellVersion = fmt.Sprintf(`(?P<%s>(%s))`, reStemcellVersionGroup, semverRegex)
255+
regexSpec.Version = versionPlaceholder
256+
regexSpec.StemcellVersion = stemcellVersionPlaceholder
249257

250-
semverFilepathRegex, err := ars.RemotePath(regexSpec)
258+
rawPath, err := ars.RemotePath(regexSpec)
251259
if err != nil {
252260
return nil, err
253261
}
254262

255-
re, err := regexp.Compile(semverFilepathRegex)
263+
escaped := regexp.QuoteMeta(rawPath)
264+
escaped = strings.ReplaceAll(escaped, regexp.QuoteMeta(versionPlaceholder), versionGroup)
265+
escaped = strings.ReplaceAll(escaped, regexp.QuoteMeta(stemcellVersionPlaceholder), stemcellVersionGroup)
266+
267+
re, err := regexp.Compile("^" + escaped + "$")
256268
return re, err
257269
}
258270

internal/component/artifactory_release_source_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,72 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() {
765765
})
766766
})
767767

768+
Describe("regex pattern matching", func() {
769+
When("a release name contains a dot and only a non-matching file is returned", func() {
770+
BeforeEach(func() {
771+
config.PathTemplate = "bosh-releases/{{.Name}}-{{.Version}}.tgz"
772+
requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword)
773+
774+
apiStorageListing := ApiStorageListing{}
775+
apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{
776+
Path: "bosh-releases",
777+
Name: "myXrelease-2.3.4.tgz",
778+
ActualSha1: "some-sha",
779+
})
780+
781+
apiStorageListingBytes, err := json.Marshal(apiStorageListing)
782+
Expect(err).NotTo(HaveOccurred())
783+
784+
artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
785+
res.WriteHeader(http.StatusOK)
786+
_, _ = io.Writer.Write(res, apiStorageListingBytes)
787+
}), requireAuth))
788+
})
789+
790+
It("does not match a file where the dot is replaced by another character", func() {
791+
_, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{
792+
Name: "my.release",
793+
Version: "2.3.4",
794+
})
795+
796+
Expect(resultErr).To(HaveOccurred())
797+
Expect(component.IsErrNotFound(resultErr)).To(BeTrue())
798+
})
799+
})
800+
801+
When("a file extension dot is replaced by another character", func() {
802+
BeforeEach(func() {
803+
config.PathTemplate = "bosh-releases/{{.Name}}-{{.Version}}.tgz"
804+
requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword)
805+
806+
apiStorageListing := ApiStorageListing{}
807+
apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{
808+
Path: "bosh-releases",
809+
Name: "mango-2.3.4Xtgz",
810+
ActualSha1: "some-sha",
811+
})
812+
813+
apiStorageListingBytes, err := json.Marshal(apiStorageListing)
814+
Expect(err).NotTo(HaveOccurred())
815+
816+
artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
817+
res.WriteHeader(http.StatusOK)
818+
_, _ = io.Writer.Write(res, apiStorageListingBytes)
819+
}), requireAuth))
820+
})
821+
822+
It("does not match the file", func() {
823+
_, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{
824+
Name: "mango",
825+
Version: "2.3.4",
826+
})
827+
828+
Expect(resultErr).To(HaveOccurred())
829+
Expect(component.IsErrNotFound(resultErr)).To(BeTrue())
830+
})
831+
})
832+
})
833+
768834
When("a bosh release is not found", func() {
769835
When("there are no files", func() {
770836
BeforeEach(func() {

0 commit comments

Comments
 (0)