Skip to content

Commit

Permalink
fixup; lazy entry reads
Browse files Browse the repository at this point in the history
  • Loading branch information
menehune23 committed Sep 22, 2021
1 parent f47c186 commit 314b163
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 86 deletions.
2 changes: 1 addition & 1 deletion postal/internal/dependency_mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (d DependencyMappingResolver) FindDependencyMapping(sha256 string) (string,

for _, binding := range bindings {
if uri, ok := binding.Entries[sha256]; ok {
return uri.String(), nil
return uri.ReadString()
}
}

Expand Down
28 changes: 16 additions & 12 deletions postal/internal/dependency_mappings_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package internal_test

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/packit/postal/internal"
Expand All @@ -15,20 +17,24 @@ import (
func testDependencyMappings(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
path string
tmpDir string
resolver internal.DependencyMappingResolver
bindingResolver *fakes.BindingResolver
err error
)

it.Before(func() {
tmpDir, err = ioutil.TempDir("", "dependency-mappings")
Expect(err).NotTo(HaveOccurred())
Expect(ioutil.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() {
Expand All @@ -38,25 +44,23 @@ func testDependencyMappings(t *testing.T, context spec.G, it spec.S) {
Name: "some-binding",
Path: "some-path",
Type: "dependency-mapping",
Entries: map[string]servicebindings.Entry{
"some-sha": servicebindings.MakeEntry([]byte("dependency-mapping-entry.tgz")),
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.MakeEntry([]byte("dependency-mapping-entry.tgz")),
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{
"some-sha": servicebindings.MakeEntry([]byte("entry.tgz")),
},
Name: "another-binding",
Path: "another-path",
Type: "another-type",
Entries: map[string]*servicebindings.Entry{},
},
}
})
Expand Down
60 changes: 44 additions & 16 deletions servicebindings/entry.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,59 @@
package servicebindings

import "bytes"
import (
"os"
)

// Entry represents the read-only content of a binding entry.
type Entry struct {
data *bytes.Buffer
path string
file *os.File
}

// MakeEntry returns an Entry from the given content.
func MakeEntry(data []byte) Entry {
return Entry{
data: bytes.NewBuffer(data),
// NewEntry returns a new Entry whose content is given by the file at the provided path.
func NewEntry(path string) *Entry {
return &Entry{
path: path,
}
}

// Bytes returns the raw content of the entry.
func (e Entry) Bytes() []byte {
return e.data.Bytes()
// ReadBytes reads the entire raw content of the entry. There is no need to call Close after calling ReadBytes.
func (e *Entry) ReadBytes() ([]byte, error) {
return os.ReadFile(e.path)
}

// String returns the content of the entry as a string.
func (e Entry) String() string {
return e.data.String()
// ReadString reads the entire content of the entry as a string. There is no need to call Close after calling
// ReadString.
func (e *Entry) ReadString() (string, error) {
bytes, err := e.ReadBytes()
if err != nil {
return "", err
}
return string(bytes), nil
}

// Read reads the next len(p) bytes from the entry or until the entry is drained. The return value n is the number of
// bytes read. If the entry has no data to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.
func (e Entry) Read(p []byte) (int, error) {
return e.data.Read(p)
// Read reads up to len(b) bytes from the entry. It returns the number of bytes read and any error encountered. At end
// of entry data, Read returns 0, io.EOF.
// Close must be called when all read operations are complete.
func (e *Entry) Read(b []byte) (int, error) {
if e.file == nil {
file, err := os.Open(e.path)
if err != nil {
return 0, err
}
e.file = file
}
return e.file.Read(b)
}

// Close closes the entry and resets it for reading. After calling Close, any subsequent calls to Read will read entry
// data from the beginning. Close is idempotent.
func (e *Entry) Close() error {
if e.file == nil {
return nil
}
defer func() {
e.file = nil
}()
return e.file.Close()
}
52 changes: 44 additions & 8 deletions servicebindings/entry_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package servicebindings_test

import (
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/packit/servicebindings"
Expand All @@ -13,24 +16,57 @@ import (
func testEntry(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
reader = servicebindings.MakeEntry([]byte("some data"))
entry *servicebindings.Entry
tmpDir string
)

it("adheres to io.Reader interface", func() {
data, err := ioutil.ReadAll(reader)
it.Before(func() {
var err error
tmpDir, err = ioutil.TempDir("", "entry")
Expect(err).NotTo(HaveOccurred())
Expect(data).To(Equal([]byte("some data")))
entryPath := filepath.Join(tmpDir, "entry")
Expect(ioutil.WriteFile(entryPath, []byte("some data"), os.ModePerm)).To(Succeed())
entry = servicebindings.NewEntry(entryPath)
})

context("Bytes", func() {
it.After(func() {
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})

context("ReadBytes", func() {
it("returns the raw bytes of the entry", func() {
Expect(reader.Bytes()).To(Equal([]byte("some data")))
Expect(entry.ReadBytes()).To(Equal([]byte("some data")))
})
})

context("String", func() {
context("ReadString", func() {
it("returns the string value of the entry", func() {
Expect(reader.String()).To(Equal("some data"))
Expect(entry.ReadString()).To(Equal("some data"))
})
})

context("usage as an io.ReadCloser", func() {
it("is assignable to io.ReadCloser", func() {
var _ io.ReadCloser = entry
})

it("can be read again after closing", func() {
data, err := ioutil.ReadAll(entry)
Expect(err).NotTo(HaveOccurred())
Expect(entry.Close()).To(Succeed())
Expect(data).To(Equal([]byte("some data")))

data, err = ioutil.ReadAll(entry)
Expect(err).NotTo(HaveOccurred())
Expect(entry.Close()).To(Succeed())
Expect(data).To(Equal([]byte("some data")))
})

it("can be closed multiple times in a row", func() {
_, err := ioutil.ReadAll(entry)
Expect(err).NotTo(HaveOccurred())
Expect(entry.Close()).To(Succeed())
Expect(entry.Close()).To(Succeed())
})
})
}
36 changes: 22 additions & 14 deletions servicebindings/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Binding struct {
Provider string

// Entries is the set of entries that make up the binding.
Entries map[string]Entry
Entries map[string]*Entry
}

// Resolver resolves service bindings according to the kubernetes binding spec:
Expand Down Expand Up @@ -139,7 +139,7 @@ func loadBinding(bindingRoot string, name string) (Binding, error) {
binding := Binding{
Name: name,
Path: filepath.Join(bindingRoot, name),
Entries: map[string]Entry{},
Entries: map[string]*Entry{},
}

entries, err := loadEntries(filepath.Join(binding.Path))
Expand All @@ -151,12 +151,18 @@ func loadBinding(bindingRoot string, name string) (Binding, error) {
if !ok {
return Binding{}, errors.New("missing 'type'")
}
binding.Type = typ.String()
binding.Type, err = typ.ReadString()
if err != nil {
return Binding{}, err
}
delete(entries, "type")

provider, ok := entries["provider"]
if ok {
binding.Provider = provider.String()
binding.Provider, err = provider.ReadString()
if err != nil {
return Binding{}, err
}
delete(entries, "provider")
}

Expand All @@ -170,7 +176,7 @@ func loadLegacyBinding(bindingRoot string, name string) (Binding, error) {
binding := Binding{
Name: name,
Path: filepath.Join(bindingRoot, name),
Entries: map[string]Entry{},
Entries: map[string]*Entry{},
}

metadata, err := loadEntries(filepath.Join(binding.Path, "metadata"))
Expand All @@ -182,14 +188,20 @@ func loadLegacyBinding(bindingRoot string, name string) (Binding, error) {
if !ok {
return Binding{}, errors.New("missing 'kind'")
}
binding.Type = typ.String()
binding.Type, err = typ.ReadString()
if err != nil {
return Binding{}, err
}
delete(metadata, "kind")

provider, ok := metadata["provider"]
if !ok {
return Binding{}, errors.New("missing 'provider'")
}
binding.Provider = provider.String()
binding.Provider, err = provider.ReadString()
if err != nil {
return Binding{}, err
}
delete(metadata, "provider")

binding.Entries = metadata
Expand All @@ -207,19 +219,15 @@ func loadLegacyBinding(bindingRoot string, name string) (Binding, error) {
return binding, nil
}

func loadEntries(path string) (map[string]Entry, error) {
entries := map[string]Entry{}
func loadEntries(path string) (map[string]*Entry, error) {
entries := map[string]*Entry{}
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}

for _, file := range files {
content, err := os.ReadFile(filepath.Join(path, file.Name()))
if err != nil {
return nil, err
}
entries[file.Name()] = MakeEntry(content)
entries[file.Name()] = NewEntry(filepath.Join(path, file.Name()))
}
return entries, nil
}
Loading

0 comments on commit 314b163

Please sign in to comment.