Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service binding resolver #228

Merged
merged 1 commit into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
.idea
coverage.out
10 changes: 5 additions & 5 deletions postal/fakes/mapping_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import "sync"

type MappingResolver struct {
FindDependencyMappingCall struct {
sync.Mutex
mutex sync.Mutex
CallCount int
Receives struct {
SHA256 string
BindingPath string
PlatformDir string
}
Returns struct {
String string
Expand All @@ -19,11 +19,11 @@ type MappingResolver struct {
}

func (f *MappingResolver) FindDependencyMapping(param1 string, param2 string) (string, error) {
f.FindDependencyMappingCall.Lock()
defer f.FindDependencyMappingCall.Unlock()
f.FindDependencyMappingCall.mutex.Lock()
defer f.FindDependencyMappingCall.mutex.Unlock()
f.FindDependencyMappingCall.CallCount++
f.FindDependencyMappingCall.Receives.SHA256 = param1
f.FindDependencyMappingCall.Receives.BindingPath = param2
f.FindDependencyMappingCall.Receives.PlatformDir = param2
if f.FindDependencyMappingCall.Stub != nil {
return f.FindDependencyMappingCall.Stub(param1, param2)
}
Expand Down
55 changes: 20 additions & 35 deletions postal/internal/dependency_mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,36 @@ package internal

import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/paketo-buildpacks/packit/servicebindings"
)

type DependencyMappingResolver struct{}
//go:generate faux --interface BindingResolver --output fakes/binding_resolver.go
type BindingResolver interface {
Resolve(typ, provider, platformDir string) ([]servicebindings.Binding, error)
}

func NewDependencyMappingResolver() DependencyMappingResolver {
return DependencyMappingResolver{}
type DependencyMappingResolver struct {
bindingResolver BindingResolver
}

// Reference file structure for bindings directory
// - bindings
// - some-binding
// - type -> dependency-mapping
// - some-sha -> some-uri
// - other-sha -> other-uri
func NewDependencyMappingResolver(bindingResolver BindingResolver) DependencyMappingResolver {
return DependencyMappingResolver{
bindingResolver: bindingResolver,
}
}

// Given a target dependency, look up if there is a matching dependency mapping at the given binding path
func (d DependencyMappingResolver) FindDependencyMapping(sha256, bindingPath string) (string, error) {
allBindings, err := filepath.Glob(filepath.Join(bindingPath, "*"))
// FindDependencyMapping looks up if there is a matching dependency mapping
func (d DependencyMappingResolver) FindDependencyMapping(sha256, platformDir string) (string, error) {
bindings, err := d.bindingResolver.Resolve("dependency-mapping", "", platformDir)
if err != nil {
return "", err
return "", fmt.Errorf("failed to resolve 'dependency-mapping' binding: %w", err)
}

for _, binding := range allBindings {
bindType, err := os.ReadFile(filepath.Join(binding, "type"))
if err != nil {
return "", fmt.Errorf("couldn't read binding type: %w", err)
}

if strings.TrimSpace(string(bindType)) == "dependency-mapping" {
if _, err := os.Stat(filepath.Join(binding, sha256)); err != nil {
if !os.IsNotExist(err) {
return "", err
}
continue
}

uri, err := os.ReadFile(filepath.Join(binding, sha256))
if err != nil {
return "", err
}
return strings.TrimSpace(string(uri)), nil
for _, binding := range bindings {
if uri, ok := binding.Entries[sha256]; ok {
return uri.ReadString()
}
}

return "", nil
}
111 changes: 43 additions & 68 deletions postal/internal/dependency_mappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,106 +6,81 @@ import (
"testing"

"github.com/paketo-buildpacks/packit/postal/internal"
"github.com/paketo-buildpacks/packit/postal/internal/fakes"
"github.com/paketo-buildpacks/packit/servicebindings"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
)

func testDependencyMappings(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
path string
resolver internal.DependencyMappingResolver
bindingPath string
err error
Expect = NewWithT(t).Expect
tmpDir string
resolver internal.DependencyMappingResolver
bindingResolver *fakes.BindingResolver
err error
)

it.Before(func() {
resolver = internal.NewDependencyMappingResolver()
bindingPath, err = os.MkdirTemp("", "bindings")
tmpDir, err = os.MkdirTemp("", "dependency-mappings")
Expect(err).NotTo(HaveOccurred())
Expect(os.WriteFile(filepath.Join(tmpDir, "entry-data"), []byte("dependency-mapping-entry.tgz"), os.ModePerm))

bindingResolver = &fakes.BindingResolver{}
resolver = internal.NewDependencyMappingResolver(bindingResolver)
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
Expect(os.RemoveAll(path)).To(Succeed())
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})

context("FindDependencyMapping", func() {
it.Before(func() {
Expect(os.MkdirAll(filepath.Join(bindingPath, "some-binding"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "some-binding", "type"), []byte("dependency-mapping"), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "some-binding", "some-sha"), []byte("dependency-mapping-entry.tgz"), 0600)).To(Succeed())

Expect(os.MkdirAll(filepath.Join(bindingPath, "other-binding"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "other-binding", "type"), []byte("dependency-mapping"), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "other-binding", "other-sha"), []byte("dependency-mapping-entry.tgz"), 0600)).To(Succeed())

Expect(os.MkdirAll(filepath.Join(bindingPath, "another-binding"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "another-binding", "type"), []byte("another type"), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "another-binding", "some-sha"), []byte("entry.tgz"), 0600)).To(Succeed())
bindingResolver.ResolveCall.Returns.BindingSlice = []servicebindings.Binding{
{
Name: "some-binding",
Path: "some-path",
Type: "dependency-mapping",
Entries: map[string]*servicebindings.Entry{
"some-sha": servicebindings.NewEntry(filepath.Join(tmpDir, "entry-data")),
},
},
{
Name: "other-binding",
Path: "other-path",
Type: "dependency-mapping",
Entries: map[string]*servicebindings.Entry{
"other-sha": servicebindings.NewEntry("some-entry-path"),
},
},
{
Name: "another-binding",
Path: "another-path",
Type: "another-type",
Entries: map[string]*servicebindings.Entry{},
},
}
})

context("given a set of bindings and a dependency", func() {
it("finds a matching dependency mappings in the platform bindings if there is one", func() {
boundDependency, err := resolver.FindDependencyMapping("some-sha", bindingPath)
boundDependency, err := resolver.FindDependencyMapping("some-sha", "some-platform-dir")
Expect(err).ToNot(HaveOccurred())
Expect(bindingResolver.ResolveCall.Receives.Typ).To(Equal("dependency-mapping"))
Expect(bindingResolver.ResolveCall.Receives.Provider).To(BeEmpty())
Expect(bindingResolver.ResolveCall.Receives.PlatformDir).To(Equal("some-platform-dir"))
Expect(boundDependency).To(Equal("dependency-mapping-entry.tgz"))
})
})

context("given a set of bindings and a dependency", func() {
it("returns an empty DependencyMapping if there is no match", func() {
boundDependency, err := resolver.FindDependencyMapping("unmatched-sha", bindingPath)
boundDependency, err := resolver.FindDependencyMapping("unmatched-sha", "")
Expect(err).ToNot(HaveOccurred())
Expect(boundDependency).To(Equal(""))
})
})
})

context("failure cases", func() {
context("when the binding path is a bad pattern", func() {
it("errors", func() {
_, err := resolver.FindDependencyMapping("some-sha", "///")
Expect(err).To(HaveOccurred())
})
})

context("when type file cannot be opened", func() {
it.Before(func() {
Expect(os.MkdirAll(filepath.Join(bindingPath, "some-binding"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "some-binding", "type"), []byte("dependency-mapping"), 0000)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "some-binding", "some-sha"), []byte("dependency-mapping-entry.tgz"), 0600)).To(Succeed())
})
it("errors", func() {
_, err := resolver.FindDependencyMapping("some-sha", bindingPath)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(ContainSubstring("couldn't read binding type")))
})
})

context("when SHA256 file cannot be stat", func() {
it.Before(func() {
Expect(os.MkdirAll(filepath.Join(bindingPath, "new-binding"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "new-binding", "type"), []byte("dependency-mapping"), 0644)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "new-binding", "some-sha"), []byte("dependency-mapping-entry.tgz"), 0644)).To(Succeed())
Expect(os.Chmod(filepath.Join(bindingPath, "new-binding", "some-sha"), 0000)).To(Succeed())
})
it("errors", func() {
_, err := resolver.FindDependencyMapping("some-sha", bindingPath)
Expect(err).To(HaveOccurred())
})
})

context("when SHA256 contents cannot be opened", func() {
it.Before(func() {
Expect(os.MkdirAll(filepath.Join(bindingPath, "some-binding"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "some-binding", "type"), []byte("dependency-mapping"), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(bindingPath, "some-binding", "some-sha"), []byte("dependency-mapping-entry.tgz"), 0000)).To(Succeed())
})
it("errors", func() {
_, err := resolver.FindDependencyMapping("some-sha", bindingPath)
Expect(err).To(HaveOccurred())
})
})
})
}
37 changes: 37 additions & 0 deletions postal/internal/fakes/binding_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fakes
ryanmoran marked this conversation as resolved.
Show resolved Hide resolved

import (
"sync"

"github.com/paketo-buildpacks/packit/servicebindings"
)

type BindingResolver struct {
ResolveCall struct {
mutex sync.Mutex
CallCount int
Receives struct {
Typ string
Provider string
PlatformDir string
}
Returns struct {
BindingSlice []servicebindings.Binding
Error error
}
Stub func(string, string, string) ([]servicebindings.Binding, error)
}
}

func (f *BindingResolver) Resolve(param1 string, param2 string, param3 string) ([]servicebindings.Binding, error) {
f.ResolveCall.mutex.Lock()
defer f.ResolveCall.mutex.Unlock()
f.ResolveCall.CallCount++
f.ResolveCall.Receives.Typ = param1
f.ResolveCall.Receives.Provider = param2
f.ResolveCall.Receives.PlatformDir = param3
if f.ResolveCall.Stub != nil {
return f.ResolveCall.Stub(param1, param2, param3)
}
return f.ResolveCall.Returns.BindingSlice, f.ResolveCall.Returns.Error
}
16 changes: 10 additions & 6 deletions postal/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"time"

"github.com/Masterminds/semver/v3"

"github.com/paketo-buildpacks/packit"
"github.com/paketo-buildpacks/packit/cargo"
"github.com/paketo-buildpacks/packit/postal/internal"
"github.com/paketo-buildpacks/packit/servicebindings"
"github.com/paketo-buildpacks/packit/vacation"
)

Expand All @@ -26,9 +28,9 @@ type Transport interface {

//go:generate faux --interface MappingResolver --output fakes/mapping_resolver.go
// MappingResolver serves as the interface that looks up platform binding provided
// dependency mappings given a SHA256 and a path to search for bindings
// dependency mappings given a SHA256
type MappingResolver interface {
FindDependencyMapping(SHA256, bindingPath string) (string, error)
FindDependencyMapping(SHA256, platformDir string) (string, error)
}

// Service provides a mechanism for resolving and installing dependencies given
Expand All @@ -38,11 +40,13 @@ type Service struct {
mappingResolver MappingResolver
}

// NewService creates an instance of a Servicel given a Transport.
// NewService creates an instance of a Service given a Transport.
func NewService(transport Transport) Service {
return Service{
transport: transport,
mappingResolver: internal.NewDependencyMappingResolver(),
transport: transport,
mappingResolver: internal.NewDependencyMappingResolver(
servicebindings.NewResolver(),
),
}
}

Expand Down Expand Up @@ -140,7 +144,7 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) {
// validated against the checksum value provided on the Dependency and will
// error if there are inconsistencies in the fetched result.
func (s Service) Deliver(dependency Dependency, cnbPath, layerPath, platformPath string) error {
dependencyMappingURI, err := s.mappingResolver.FindDependencyMapping(dependency.SHA256, filepath.Join(platformPath, "bindings"))
dependencyMappingURI, err := s.mappingResolver.FindDependencyMapping(dependency.SHA256, platformPath)
if err != nil {
return fmt.Errorf("failure checking for dependency mappings: %s", err)
}
Expand Down
Loading