Skip to content

Commit

Permalink
Adds jam cli for building buildpacks
Browse files Browse the repository at this point in the history
[#170138077]

Co-authored-by: Forest Eckhardt <[email protected]>
Co-authored-by: Daniel Thornton <[email protected]>
  • Loading branch information
3 people committed Dec 11, 2019
1 parent dbdc3b5 commit ecdc5cc
Show file tree
Hide file tree
Showing 33 changed files with 1,511 additions and 126 deletions.
1 change: 1 addition & 0 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/cloudfoundry/packit/fakes"
"github.com/sclevine/spec"

. "github.com/cloudfoundry/packit/matchers"
. "github.com/onsi/gomega"
)

Expand Down
45 changes: 45 additions & 0 deletions cargo/buildpack_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cargo

import (
"os"

"github.com/pelletier/go-toml"
)

type Config struct {
API string `toml:"api"`
Buildpack ConfigBuildpack `toml:"buildpack"`
Metadata ConfigMetadata `toml:"metadata"`
}

type ConfigBuildpack struct {
ID string `toml:"id"`
Name string `toml:"name"`
Version string `toml:"version"`
}

type ConfigMetadata struct {
IncludeFiles []string `toml:"include_files"`
PrePackage string `toml:"pre_package"`
}

type BuildpackParser struct{}

func NewBuildpackParser() BuildpackParser {
return BuildpackParser{}
}

func (p BuildpackParser) Parse(path string) (Config, error) {
file, err := os.Open(path)
if err != nil {
return Config{}, err
}

var config Config
err = toml.NewDecoder(file).Decode(&config)
if err != nil {
return Config{}, err
}

return config, nil
}
91 changes: 91 additions & 0 deletions cargo/buildpack_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cargo_test

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

"github.com/cloudfoundry/packit/cargo"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
)

func testBuildpackParser(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

path string
parser cargo.BuildpackParser
)

it.Before(func() {
file, err := ioutil.TempFile("", "buildpack.toml")
Expect(err).NotTo(HaveOccurred())

_, err = file.WriteString(`api = "0.2"
[buildpack]
id = "some-buildpack-id"
name = "some-buildpack-name"
version = "some-buildpack-version"
[metadata]
include_files = ["some-include-file", "other-include-file"]
pre_package = "some-pre-package-script.sh"`)
Expect(err).NotTo(HaveOccurred())

Expect(file.Close()).To(Succeed())

path = file.Name()

parser = cargo.NewBuildpackParser()
})

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

context("Parse", func() {
it("parses a given buildpack.toml", func() {
config, err := parser.Parse(path)
Expect(err).NotTo(HaveOccurred())
Expect(config).To(Equal(cargo.Config{
API: "0.2",
Buildpack: cargo.ConfigBuildpack{
ID: "some-buildpack-id",
Name: "some-buildpack-name",
Version: "some-buildpack-version",
},
Metadata: cargo.ConfigMetadata{
IncludeFiles: []string{
"some-include-file",
"other-include-file",
},
PrePackage: "some-pre-package-script.sh",
},
}))
})

context("when the buildpack.toml does not exist", func() {
it.Before(func() {
Expect(os.Remove(path)).To(Succeed())
})

it("returns an error", func() {
_, err := parser.Parse(path)
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
})
})

context("when the buildpack.toml is malformed", func() {
it.Before(func() {
Expect(ioutil.WriteFile(path, []byte("%%%"), 0644)).To(Succeed())
})

it("returns an error", func() {
_, err := parser.Parse(path)
Expect(err).To(MatchError(ContainSubstring("keys cannot contain % character")))
})
})
})
}
34 changes: 34 additions & 0 deletions cargo/fakes/executable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package fakes

import (
"sync"

"github.com/cloudfoundry/packit/pexec"
)

type Executable struct {
ExecuteCall struct {
sync.Mutex
CallCount int
Receives struct {
Execution pexec.Execution
}
Returns struct {
StdOut string
StdError string
Err error
}
Stub func(pexec.Execution) (string, string, error)
}
}

func (f *Executable) Execute(param1 pexec.Execution) (string, string, error) {
f.ExecuteCall.Lock()
defer f.ExecuteCall.Unlock()
f.ExecuteCall.CallCount++
f.ExecuteCall.Receives.Execution = param1
if f.ExecuteCall.Stub != nil {
return f.ExecuteCall.Stub(param1)
}
return f.ExecuteCall.Returns.StdOut, f.ExecuteCall.Returns.StdError, f.ExecuteCall.Returns.Err
}
57 changes: 57 additions & 0 deletions cargo/file_bundler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cargo

import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"

"github.com/pelletier/go-toml"
)

type FileBundler struct{}

func NewFileBundler() FileBundler {
return FileBundler{}
}

func (b FileBundler) Bundle(root string, paths []string, config Config) ([]File, error) {
var files []File
for _, path := range paths {
file := File{Name: path}

switch path {
case "buildpack.toml":
buf := bytes.NewBuffer(nil)
err := toml.NewEncoder(buf).Encode(config)
if err != nil {
return nil, fmt.Errorf("error encoding buildpack.toml: %s", err)
}

file.ReadCloser = ioutil.NopCloser(buf)
file.Size = int64(buf.Len())
file.Mode = int64(0644)

default:
fd, err := os.Open(filepath.Join(root, path))
if err != nil {
return nil, fmt.Errorf("error opening included file: %s", err)
}

info, err := fd.Stat()
if err != nil {
return nil, fmt.Errorf("error stating included file: %s", err)
}

file.ReadCloser = fd
file.Size = info.Size()
file.Mode = int64(info.Mode())

}

files = append(files, file)
}

return files, nil
}
90 changes: 90 additions & 0 deletions cargo/file_bundler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package cargo_test

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

"github.com/cloudfoundry/packit/cargo"
"github.com/sclevine/spec"

. "github.com/cloudfoundry/packit/matchers"
. "github.com/onsi/gomega"
)

func testFileBundler(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

fileBundler cargo.FileBundler
)

it.Before(func() {
fileBundler = cargo.NewFileBundler()
})

context("Bundle", func() {
it("returns a list of cargo files", func() {
files, err := fileBundler.Bundle(filepath.Join("jam", "testdata", "example-cnb"), []string{"bin/build", "bin/detect", "buildpack.toml"}, cargo.Config{
API: "0.2",
Buildpack: cargo.ConfigBuildpack{
ID: "other-buildpack-id",
Name: "other-buildpack-name",
Version: "other-buildpack-version",
},
Metadata: cargo.ConfigMetadata{
IncludeFiles: []string{
"bin/build",
"bin/detect",
"buildpack.toml",
},
PrePackage: "some-pre-package-script.sh",
},
})
Expect(err).NotTo(HaveOccurred())

Expect(files).To(HaveLen(3))

Expect(files[0].Name).To(Equal("bin/build"))
Expect(files[0].Size).To(Equal(int64(14)))
Expect(files[0].Mode).To(Equal(int64(0755)))

content, err := ioutil.ReadAll(files[0])
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal("build-contents"))

Expect(files[1].Name).To(Equal("bin/detect"))
Expect(files[1].Size).To(Equal(int64(15)))
Expect(files[1].Mode).To(Equal(int64(0755)))

content, err = ioutil.ReadAll(files[1])
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal("detect-contents"))

Expect(files[2].Name).To(Equal("buildpack.toml"))
Expect(files[2].Size).To(Equal(int64(242)))
Expect(files[2].Mode).To(Equal(int64(0644)))

content, err = ioutil.ReadAll(files[2])
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(MatchTOML(`api = "0.2"
[buildpack]
id = "other-buildpack-id"
name = "other-buildpack-name"
version = "other-buildpack-version"
[metadata]
include_files = ["bin/build", "bin/detect", "buildpack.toml"]
pre_package = "some-pre-package-script.sh"`))
})

context("error cases", func() {
context("when included file does not exist", func() {
it("fails", func() {
_, err := fileBundler.Bundle(filepath.Join("jam", "testdata", "example-cnb"), []string{"bin/fake/build", "bin/detect", "buildpack.toml"}, cargo.Config{})
Expect(err).To(MatchError(ContainSubstring("error opening included file:")))
})
})
})
})
}
48 changes: 48 additions & 0 deletions cargo/init_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package cargo_test

import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"testing"

"github.com/sclevine/spec"
Expand All @@ -9,7 +15,49 @@ import (

func TestUnitCargo(t *testing.T) {
suite := spec.New("cargo", spec.Report(report.Terminal{}))
suite("BuildpackParser", testBuildpackParser)
suite("FileBundler", testFileBundler)
suite("TarBuilder", testTarBuilder)
suite("Transport", testTransport)
suite("ValidatedReader", testValidatedReader)
suite("PrePackager", testPrePackager)
suite.Run(t)
}

func ExtractFile(file *os.File, name string) ([]byte, *tar.Header, error) {
_, err := file.Seek(0, 0)
if err != nil {
return nil, nil, err
}

//TODO: Replace me with decompression library
gzr, err := gzip.NewReader(file)
if err != nil {
return nil, nil, err
}
defer gzr.Close()

tr := tar.NewReader(gzr)

for {
hdr, err := tr.Next()
if err != nil {
if err == io.EOF {
break
}
return nil, nil, err
}

if hdr.Name == name {
contents, err := ioutil.ReadAll(tr)
if err != nil {
return nil, nil, err
}

return contents, hdr, nil
}

}

return nil, nil, fmt.Errorf("no such file: %s", name)
}
Loading

0 comments on commit ecdc5cc

Please sign in to comment.