From 5e98483dc2093cd476db4843d560eb0f31c2f9f2 Mon Sep 17 00:00:00 2001 From: bitgully <32452884+bitgully@users.noreply.github.com> Date: Sat, 12 Oct 2024 07:56:00 +0200 Subject: [PATCH] skip-path feature added for dependency mirrors --- postal/internal/dependency_mirror.go | 40 ++++++++- postal/internal/dependency_mirror_test.go | 104 ++++++++++++++++++++++ 2 files changed, 142 insertions(+), 2 deletions(-) diff --git a/postal/internal/dependency_mirror.go b/postal/internal/dependency_mirror.go index f8653c77..ca4d750e 100644 --- a/postal/internal/dependency_mirror.go +++ b/postal/internal/dependency_mirror.go @@ -17,8 +17,44 @@ func NewDependencyMirrorResolver(bindingResolver BindingResolver) DependencyMirr } } +// Parses a raw mirror string into a map of arguments. +func getMirrorArgs(mirror string) map[string]string { + + mirrorArgs := map[string]string{ + "mirror": mirror, + "skip-path": "", + } + + // Split mirror string at commas and extract specified arguments. + for _, arg := range strings.Split(mirror, ",") { + argPair := strings.SplitN(arg, "=", 2) + // If a URI is provided without the key 'mirror=', still treat it as the 'mirror' argument. + // This addresses backwards compatibility and user experience as most mirrors won't need any additional arguments. + if len(argPair) == 1 && (strings.HasPrefix(argPair[0], "https") || strings.HasPrefix(argPair[0], "file")) { + mirrorArgs["mirror"] = argPair[0] + } + // Add all provided arguments to key/value map. + if len(argPair) == 2 { + mirrorArgs[argPair[0]] = argPair[1] + } + } + + // Unescape mirror arguments to support URL-encoded strings. + tmp, err := url.PathUnescape(mirrorArgs["mirror"]) + if err == nil { + mirrorArgs["mirror"] = tmp + } + tmp, err = url.PathUnescape(mirrorArgs["skip-path"]) + if err == nil { + mirrorArgs["skip-path"] = tmp + } + + return mirrorArgs +} + func formatAndVerifyMirror(mirror, uri string) (string, error) { - mirrorURL, err := url.Parse(mirror) + mirrorArgs := getMirrorArgs(mirror) + mirrorURL, err := url.Parse(mirrorArgs["mirror"]) if err != nil { return "", err } @@ -32,7 +68,7 @@ func formatAndVerifyMirror(mirror, uri string) (string, error) { return "", fmt.Errorf("invalid mirror scheme") } - mirrorURL.Path = strings.Replace(mirrorURL.Path, "{originalHost}", uriURL.Hostname(), 1) + uriURL.Path + mirrorURL.Path = strings.Replace(mirrorURL.Path, "{originalHost}", uriURL.Hostname(), 1) + strings.Replace(uriURL.Path, mirrorArgs["skip-path"], "", 1) return mirrorURL.String(), nil } diff --git a/postal/internal/dependency_mirror_test.go b/postal/internal/dependency_mirror_test.go index 3efa27fa..fa7057dd 100644 --- a/postal/internal/dependency_mirror_test.go +++ b/postal/internal/dependency_mirror_test.go @@ -123,6 +123,73 @@ func testDependencyMirror(t *testing.T, context spec.G, it spec.S) { }) }) + context("via binding with additional arguments", func() { + it.Before(func() { + tmpDir, err = os.MkdirTemp("", "dependency-mirror") + Expect(err).NotTo(HaveOccurred()) + Expect(os.WriteFile(filepath.Join(tmpDir, "type"), []byte("dependency-mirror"), os.ModePerm)) + + bindingResolver = &fakes.BindingResolver{} + resolver = internal.NewDependencyMirrorResolver(bindingResolver) + + bindingResolver.ResolveCall.Returns.BindingSlice = []servicebindings.Binding{ + { + Name: "some-binding", + Path: "some-path", + Type: "dependency-mirror", + Entries: map[string]*servicebindings.Entry{ + "default": servicebindings.NewEntry(filepath.Join(tmpDir, "default")), + }, + }, + } + }) + + it.After(func() { + Expect(os.RemoveAll(tmpDir)).To(Succeed()) + }) + + context("respects skip-path argument", func() { + it.Before(func() { + Expect(os.WriteFile(filepath.Join(tmpDir, "github.com"), []byte("mirror=https://mirror.example.org/public-github,skip-path=/path-to-skip"), os.ModePerm)) + Expect(os.WriteFile(filepath.Join(tmpDir, "nodejs.org"), []byte("https://mirror.example.org/node-dist,skip-path=/path-to-skip"), os.ModePerm)) + Expect(os.WriteFile(filepath.Join(tmpDir, "maven.org"), []byte("https://user%3Apa%24%24word%2C%40mirror.example.org%2Fmaven,skip-path=%2Fpath%20to%20skip"), os.ModePerm)) + + bindingResolver.ResolveCall.Returns.BindingSlice[0].Entries = map[string]*servicebindings.Entry{ + "github.com": servicebindings.NewEntry(filepath.Join(tmpDir, "github.com")), + "nodejs.org": servicebindings.NewEntry(filepath.Join(tmpDir, "nodejs.org")), + "maven.org": servicebindings.NewEntry(filepath.Join(tmpDir, "maven.org")), + } + }) + + it("sets mirror excluding a path segment with 'mirror' argument", func() { + boundDependency, err := resolver.FindDependencyMirror("https://github.com/path-to-skip/dep.tgz", "some-platform-dir") + Expect(err).ToNot(HaveOccurred()) + Expect(bindingResolver.ResolveCall.Receives.Typ).To(Equal("dependency-mirror")) + Expect(bindingResolver.ResolveCall.Receives.Provider).To(BeEmpty()) + Expect(bindingResolver.ResolveCall.Receives.PlatformDir).To(Equal("some-platform-dir")) + Expect(boundDependency).To(Equal("https://mirror.example.org/public-github/dep.tgz")) + }) + + it("sets mirror excluding a path segment without 'mirror' argument", func() { + boundDependency, err := resolver.FindDependencyMirror("https://nodejs.org/path-to-skip/dep.tgz", "some-platform-dir") + Expect(err).ToNot(HaveOccurred()) + Expect(bindingResolver.ResolveCall.Receives.Typ).To(Equal("dependency-mirror")) + Expect(bindingResolver.ResolveCall.Receives.Provider).To(BeEmpty()) + Expect(bindingResolver.ResolveCall.Receives.PlatformDir).To(Equal("some-platform-dir")) + Expect(boundDependency).To(Equal("https://mirror.example.org/node-dist/dep.tgz")) + }) + + it("sets mirror excluding a path segment using URL encoding", func() { + boundDependency, err := resolver.FindDependencyMirror("https://maven.org/path to skip/dep.tgz", "some-platform-dir") + Expect(err).ToNot(HaveOccurred()) + Expect(bindingResolver.ResolveCall.Receives.Typ).To(Equal("dependency-mirror")) + Expect(bindingResolver.ResolveCall.Receives.Provider).To(BeEmpty()) + Expect(bindingResolver.ResolveCall.Receives.PlatformDir).To(Equal("some-platform-dir")) + Expect(boundDependency).To(Equal("https://user:pa$$word,@mirror.example.org/maven/dep.tgz")) + }) + }) + }) + context("via environment variables", func() { it.Before(func() { Expect(os.Setenv("BP_DEPENDENCY_MIRROR", "https://mirror.example.org/{originalHost}")) @@ -197,6 +264,43 @@ func testDependencyMirror(t *testing.T, context spec.G, it spec.S) { }) }) + context("via environment variables with additional arguments", func() { + context("respects skip-path argument", func() { + it.Before(func() { + Expect(os.Setenv("BP_DEPENDENCY_MIRROR_GITHUB_COM", "mirror=https://mirror.example.org/public-github,skip-path=/path-to-skip")) + Expect(os.Setenv("BP_DEPENDENCY_MIRROR_NODEJS_ORG", "https://mirror.example.org/node-dist,skip-path=/path-to-skip")) + Expect(os.Setenv("BP_DEPENDENCY_MIRROR_MAVEN_ORG", "https://user%3Apa%24%24word%2C%40mirror.example.org%2Fmaven,skip-path=%2Fpath%20to%20skip")) + + bindingResolver = &fakes.BindingResolver{} + resolver = internal.NewDependencyMirrorResolver(bindingResolver) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_DEPENDENCY_MIRROR_GITHUB_COM")) + Expect(os.Unsetenv("BP_DEPENDENCY_MIRROR_NODEJS_ORG")) + Expect(os.Unsetenv("BP_DEPENDENCY_MIRROR_MAVEN_ORG")) + }) + + it("sets mirror excluding a path segment with 'mirror' argument", func() { + boundDependency, err := resolver.FindDependencyMirror("https://github.com/path-to-skip/dep.tgz", "some-platform-dir") + Expect(err).ToNot(HaveOccurred()) + Expect(boundDependency).To(Equal("https://mirror.example.org/public-github/dep.tgz")) + }) + + it("sets mirror excluding a path segment without 'mirror' argument", func() { + boundDependency, err := resolver.FindDependencyMirror("https://nodejs.org/path-to-skip/dep.tgz", "some-platform-dir") + Expect(err).ToNot(HaveOccurred()) + Expect(boundDependency).To(Equal("https://mirror.example.org/node-dist/dep.tgz")) + }) + + it("sets mirror excluding a path segment using URL encoding", func() { + boundDependency, err := resolver.FindDependencyMirror("https://maven.org/path to skip/dep.tgz", "some-platform-dir") + Expect(err).ToNot(HaveOccurred()) + Expect(boundDependency).To(Equal("https://user:pa$$word,@mirror.example.org/maven/dep.tgz")) + }) + }) + }) + context("when mirror is provided by both bindings and environment variables", func() { it.Before(func() { tmpDir, err = os.MkdirTemp("", "dependency-mirror")