Skip to content

Commit

Permalink
Adds service binding resolver
Browse files Browse the repository at this point in the history
Signed-off-by: Brayan Henao <bhenao@vmware.com>
  • Loading branch information
menehune23 authored and ForestEckhardt committed Sep 27, 2021
1 parent 6b22133 commit d600cb5
Show file tree
Hide file tree
Showing 12 changed files with 1,001 additions and 163 deletions.
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

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

0 comments on commit d600cb5

Please sign in to comment.