Skip to content

Commit c942b25

Browse files
authored
Merge pull request #1166 from buildpacks/jkutner/rfc-0048
Added support for inline buildpacks Signed-off-by: David Freilich <[email protected]>
2 parents 2ea71d7 + 7f12ca8 commit c942b25

File tree

4 files changed

+274
-38
lines changed

4 files changed

+274
-38
lines changed

build.go

+63-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/rand"
66
"fmt"
7+
"io/ioutil"
78
"os"
89
"path/filepath"
910
"sort"
@@ -235,7 +236,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
235236
return err
236237
}
237238

238-
fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), opts)
239+
fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), bldr.StackID, opts)
239240
if err != nil {
240241
return err
241242
}
@@ -645,7 +646,7 @@ func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig {
645646
// ----------
646647
// - group:
647648
// - A
648-
func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.BuildpackInfo, builderOrder dist.Order, opts BuildOptions) (fetchedBPs []dist.Buildpack, order dist.Order, err error) {
649+
func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.BuildpackInfo, builderOrder dist.Order, stackID string, opts BuildOptions) (fetchedBPs []dist.Buildpack, order dist.Order, err error) {
649650
pullPolicy := opts.PullPolicy
650651
publish := opts.Publish
651652
registry := opts.Registry
@@ -657,10 +658,23 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima
657658
relativeBaseDir = opts.ProjectDescriptorBaseDir
658659

659660
for _, bp := range opts.ProjectDescriptor.Build.Buildpacks {
660-
if bp.URI == "" {
661-
declaredBPs = append(declaredBPs, fmt.Sprintf("%s@%s", bp.ID, bp.Version))
662-
} else {
661+
switch {
662+
case bp.ID != "" && bp.Script.Inline != "" && bp.Version == "" && bp.URI == "":
663+
if bp.Script.API == "" {
664+
return nil, nil, errors.New("Missing API version for inline buildpack")
665+
}
666+
667+
pathToInlineBuildpack, err := createInlineBuildpack(bp, stackID)
668+
if err != nil {
669+
return nil, nil, errors.Wrap(err, "Could not create temporary inline buildpack")
670+
}
671+
declaredBPs = append(declaredBPs, pathToInlineBuildpack)
672+
case bp.URI != "":
663673
declaredBPs = append(declaredBPs, bp.URI)
674+
case bp.ID != "" && bp.Version != "":
675+
declaredBPs = append(declaredBPs, fmt.Sprintf("%s@%s", bp.ID, bp.Version))
676+
default:
677+
return nil, nil, errors.New("Invalid buildpack defined in project descriptor")
664678
}
665679
}
666680
}
@@ -905,3 +919,47 @@ func parseDigestFromImageID(id imgutil.Identifier) string {
905919
digest = strings.TrimPrefix(digest, "sha256:")
906920
return fmt.Sprintf("sha256:%s", digest)
907921
}
922+
923+
func createInlineBuildpack(bp project.Buildpack, stackID string) (string, error) {
924+
pathToInlineBuilpack, err := ioutil.TempDir("", "inline-cnb")
925+
if err != nil {
926+
return pathToInlineBuilpack, err
927+
}
928+
929+
if err = createBuildpackTOML(pathToInlineBuilpack, bp.ID, "0.0.0", bp.Script.API, []dist.Stack{{ID: stackID}}, nil); err != nil {
930+
return pathToInlineBuilpack, err
931+
}
932+
933+
shell := bp.Script.Shell
934+
if shell == "" {
935+
shell = "/bin/sh"
936+
}
937+
938+
binBuild := fmt.Sprintf(`#!%s
939+
940+
%s
941+
`, shell, bp.Script.Inline)
942+
943+
binDetect := fmt.Sprintf(`#!%s
944+
945+
exit 0
946+
`, shell)
947+
948+
if err = createBinScript(pathToInlineBuilpack, "build", binBuild, nil); err != nil {
949+
return pathToInlineBuilpack, err
950+
}
951+
952+
if err = createBinScript(pathToInlineBuilpack, "build.bat", bp.Script.Inline, nil); err != nil {
953+
return pathToInlineBuilpack, err
954+
}
955+
956+
if err = createBinScript(pathToInlineBuilpack, "detect", binDetect, nil); err != nil {
957+
return pathToInlineBuilpack, err
958+
}
959+
960+
if err = createBinScript(pathToInlineBuilpack, "detect.bat", bp.Script.Inline, nil); err != nil {
961+
return pathToInlineBuilpack, err
962+
}
963+
964+
return pathToInlineBuilpack, nil
965+
}

build_test.go

+159
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
ilogging "github.com/buildpacks/pack/internal/logging"
4343
rg "github.com/buildpacks/pack/internal/registry"
4444
"github.com/buildpacks/pack/internal/style"
45+
"github.com/buildpacks/pack/project"
4546
h "github.com/buildpacks/pack/testhelpers"
4647
)
4748

@@ -1230,6 +1231,36 @@ func testBuild(t *testing.T, when spec.G, it spec.S) {
12301231
{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"},
12311232
})
12321233
})
1234+
1235+
it("adds the buildpack from the project descriptor", func() {
1236+
err := subject.Build(context.TODO(), BuildOptions{
1237+
Image: "some/app",
1238+
Builder: defaultBuilderName,
1239+
ClearCache: true,
1240+
ProjectDescriptor: project.Descriptor{
1241+
Build: project.Build{
1242+
Buildpacks: []project.Buildpack{{
1243+
URI: server.URL(),
1244+
}},
1245+
},
1246+
},
1247+
})
1248+
1249+
h.AssertNil(t, err)
1250+
h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name())
1251+
bldr, err := builder.FromImage(defaultBuilderImage)
1252+
h.AssertNil(t, err)
1253+
h.AssertEq(t, bldr.Order(), dist.Order{
1254+
{Group: []dist.BuildpackRef{
1255+
{BuildpackInfo: dist.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}},
1256+
}},
1257+
})
1258+
h.AssertEq(t, bldr.Buildpacks(), []dist.BuildpackInfo{
1259+
{ID: "buildpack.1.id", Version: "buildpack.1.version"},
1260+
{ID: "buildpack.2.id", Version: "buildpack.2.version"},
1261+
{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"},
1262+
})
1263+
})
12331264
})
12341265

12351266
when("added buildpack's mixins are not satisfied", func() {
@@ -1251,6 +1282,134 @@ func testBuild(t *testing.T, when spec.G, it spec.S) {
12511282
})
12521283
})
12531284

1285+
when("buildpack is inline", func() {
1286+
var (
1287+
tmpDir string
1288+
)
1289+
1290+
it.Before(func() {
1291+
var err error
1292+
tmpDir, err = ioutil.TempDir("", "project-desc")
1293+
h.AssertNil(t, err)
1294+
})
1295+
1296+
it.After(func() {
1297+
err := os.RemoveAll(tmpDir)
1298+
h.AssertNil(t, err)
1299+
})
1300+
1301+
it("all buildpacks are added to ephemeral builder", func() {
1302+
err := subject.Build(context.TODO(), BuildOptions{
1303+
Image: "some/app",
1304+
Builder: defaultBuilderName,
1305+
ClearCache: true,
1306+
ProjectDescriptor: project.Descriptor{
1307+
Build: project.Build{
1308+
Buildpacks: []project.Buildpack{{
1309+
ID: "my/inline",
1310+
Script: project.Script{
1311+
API: "0.4",
1312+
Inline: "touch foo.txt",
1313+
},
1314+
}},
1315+
},
1316+
},
1317+
ProjectDescriptorBaseDir: tmpDir,
1318+
})
1319+
1320+
h.AssertNil(t, err)
1321+
h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name())
1322+
bldr, err := builder.FromImage(defaultBuilderImage)
1323+
h.AssertNil(t, err)
1324+
h.AssertEq(t, bldr.Order(), dist.Order{
1325+
{Group: []dist.BuildpackRef{
1326+
{BuildpackInfo: dist.BuildpackInfo{ID: "my/inline", Version: "0.0.0"}},
1327+
}},
1328+
})
1329+
h.AssertEq(t, bldr.Buildpacks(), []dist.BuildpackInfo{
1330+
{ID: "buildpack.1.id", Version: "buildpack.1.version"},
1331+
{ID: "buildpack.2.id", Version: "buildpack.2.version"},
1332+
{ID: "my/inline", Version: "0.0.0"},
1333+
})
1334+
})
1335+
1336+
it("fails if there is no API", func() {
1337+
err := subject.Build(context.TODO(), BuildOptions{
1338+
Image: "some/app",
1339+
Builder: defaultBuilderName,
1340+
ClearCache: true,
1341+
ProjectDescriptor: project.Descriptor{
1342+
Build: project.Build{
1343+
Buildpacks: []project.Buildpack{{
1344+
ID: "my/inline",
1345+
Script: project.Script{
1346+
Inline: "touch foo.txt",
1347+
},
1348+
}},
1349+
},
1350+
},
1351+
ProjectDescriptorBaseDir: tmpDir,
1352+
})
1353+
1354+
h.AssertEq(t, "Missing API version for inline buildpack", err.Error())
1355+
})
1356+
1357+
it("fails if there is no ID", func() {
1358+
err := subject.Build(context.TODO(), BuildOptions{
1359+
Image: "some/app",
1360+
Builder: defaultBuilderName,
1361+
ClearCache: true,
1362+
ProjectDescriptor: project.Descriptor{
1363+
Build: project.Build{
1364+
Buildpacks: []project.Buildpack{{
1365+
Script: project.Script{
1366+
API: "0.4",
1367+
Inline: "touch foo.txt",
1368+
},
1369+
}},
1370+
},
1371+
},
1372+
ProjectDescriptorBaseDir: tmpDir,
1373+
})
1374+
1375+
h.AssertEq(t, "Invalid buildpack defined in project descriptor", err.Error())
1376+
})
1377+
1378+
it("ignores script if there is an id and version", func() {
1379+
err := subject.Build(context.TODO(), BuildOptions{
1380+
Image: "some/app",
1381+
Builder: defaultBuilderName,
1382+
ClearCache: true,
1383+
ProjectDescriptor: project.Descriptor{
1384+
Build: project.Build{
1385+
Buildpacks: []project.Buildpack{{
1386+
ID: "buildpack.1.id",
1387+
Version: "buildpack.1.version",
1388+
Script: project.Script{
1389+
Inline: "touch foo.txt",
1390+
},
1391+
}},
1392+
},
1393+
},
1394+
ProjectDescriptorBaseDir: tmpDir,
1395+
})
1396+
1397+
h.AssertNil(t, err)
1398+
h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name())
1399+
bldr, err := builder.FromImage(defaultBuilderImage)
1400+
h.AssertNil(t, err)
1401+
h.AssertEq(t, bldr.Order(), dist.Order{
1402+
{Group: []dist.BuildpackRef{
1403+
{BuildpackInfo: dist.BuildpackInfo{ID: "buildpack.1.id", Version: "buildpack.1.version"}},
1404+
}},
1405+
})
1406+
h.AssertEq(t, bldr.Buildpacks(), []dist.BuildpackInfo{
1407+
{ID: "buildpack.1.id", Version: "buildpack.1.version"},
1408+
{ID: "buildpack.2.id", Version: "buildpack.2.version"},
1409+
})
1410+
})
1411+
})
1412+
12541413
when("buildpack is from a registry", func() {
12551414
var (
12561415
fakePackage *fakes.Image

new_buildpack.go

+45-33
Original file line numberDiff line numberDiff line change
@@ -49,41 +49,10 @@ type NewBuildpackOptions struct {
4949
}
5050

5151
func (c *Client) NewBuildpack(ctx context.Context, opts NewBuildpackOptions) error {
52-
api, err := api.NewVersion(opts.API)
52+
err := createBuildpackTOML(opts.Path, opts.ID, opts.Version, opts.API, opts.Stacks, c)
5353
if err != nil {
5454
return err
5555
}
56-
57-
buildpackTOML := dist.BuildpackDescriptor{
58-
API: api,
59-
Stacks: opts.Stacks,
60-
Info: dist.BuildpackInfo{
61-
ID: opts.ID,
62-
Version: opts.Version,
63-
},
64-
}
65-
66-
// The following line's comment is for gosec, it will ignore rule 301 in this case
67-
// G301: Expect directory permissions to be 0750 or less
68-
/* #nosec G301 */
69-
if err := os.MkdirAll(opts.Path, 0755); err != nil {
70-
return err
71-
}
72-
73-
buildpackTOMLPath := filepath.Join(opts.Path, "buildpack.toml")
74-
_, err = os.Stat(buildpackTOMLPath)
75-
if os.IsNotExist(err) {
76-
f, err := os.Create(buildpackTOMLPath)
77-
if err != nil {
78-
return err
79-
}
80-
if err := toml.NewEncoder(f).Encode(buildpackTOML); err != nil {
81-
return err
82-
}
83-
defer f.Close()
84-
c.logger.Infof(" %s buildpack.toml", style.Symbol("create"))
85-
}
86-
8756
return createBashBuildpack(opts.Path, c)
8857
}
8958

@@ -119,7 +88,50 @@ func createBinScript(path, name, contents string, c *Client) error {
11988
return err
12089
}
12190

122-
c.logger.Infof(" %s bin/%s", style.Symbol("create"), name)
91+
if c != nil {
92+
c.logger.Infof(" %s bin/%s", style.Symbol("create"), name)
93+
}
94+
}
95+
return nil
96+
}
97+
98+
func createBuildpackTOML(path, id, version, apiStr string, stacks []dist.Stack, c *Client) error {
99+
api, err := api.NewVersion(apiStr)
100+
if err != nil {
101+
return err
102+
}
103+
104+
buildpackTOML := dist.BuildpackDescriptor{
105+
API: api,
106+
Stacks: stacks,
107+
Info: dist.BuildpackInfo{
108+
ID: id,
109+
Version: version,
110+
},
111+
}
112+
113+
// The following line's comment is for gosec, it will ignore rule 301 in this case
114+
// G301: Expect directory permissions to be 0750 or less
115+
/* #nosec G301 */
116+
if err := os.MkdirAll(path, 0755); err != nil {
117+
return err
123118
}
119+
120+
buildpackTOMLPath := filepath.Join(path, "buildpack.toml")
121+
_, err = os.Stat(buildpackTOMLPath)
122+
if os.IsNotExist(err) {
123+
f, err := os.Create(buildpackTOMLPath)
124+
if err != nil {
125+
return err
126+
}
127+
if err := toml.NewEncoder(f).Encode(buildpackTOML); err != nil {
128+
return err
129+
}
130+
defer f.Close()
131+
if c != nil {
132+
c.logger.Infof(" %s buildpack.toml", style.Symbol("create"))
133+
}
134+
}
135+
124136
return nil
125137
}

project/project.go

+7
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,17 @@ import (
1010
"github.com/buildpacks/pack/internal/dist"
1111
)
1212

13+
type Script struct {
14+
API string `toml:"api"`
15+
Inline string `toml:"inline"`
16+
Shell string `toml:"shell"`
17+
}
18+
1319
type Buildpack struct {
1420
ID string `toml:"id"`
1521
Version string `toml:"version"`
1622
URI string `toml:"uri"`
23+
Script Script `toml:"script"`
1724
}
1825

1926
type EnvVar struct {

0 commit comments

Comments
 (0)