Skip to content

Commit

Permalink
Provide buildpack create command
Browse files Browse the repository at this point in the history
Implement RFC 212 to provide a more general mechanism to provide
project scaffolding for new buildpacks.

Signed-off-by: Aidan Delaney <[email protected]>
  • Loading branch information
AidanDelaney committed Feb 16, 2023
1 parent de786a2 commit 6e320e4
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/apex/log v1.9.0
github.com/buildpacks/imgutil v0.0.0-20221128174954-533a87656dc4
github.com/buildpacks/lifecycle v0.15.3
github.com/buildpacks/scafall v0.0.0-20230124113818-e2ea3479ac3d
github.com/docker/cli v23.0.0+incompatible
github.com/docker/docker v20.10.22+incompatible
github.com/docker/go-connections v0.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ github.com/buildpacks/imgutil v0.0.0-20221128174954-533a87656dc4 h1:JrC06zJNkuOG
github.com/buildpacks/imgutil v0.0.0-20221128174954-533a87656dc4/go.mod h1:7XJyKf8MwQfHcsjjA9et24BWN3gE1FztxnN8de54mL0=
github.com/buildpacks/lifecycle v0.15.3 h1:jjYc0sclsxyCpBQoMkAQKynRrhuRbXXCj9eH7ApUOYQ=
github.com/buildpacks/lifecycle v0.15.3/go.mod h1:vNhpm82BD9JlzZbFP9JXYt2tMBlIf9EA20VtZ0PkyRE=
github.com/buildpacks/scafall v0.0.0-20230124113818-e2ea3479ac3d h1:o0CzZvpd9k9bZE5v/vI6AvW4msQneOH6t0fKDNEM+7Y=
github.com/buildpacks/scafall v0.0.0-20230124113818-e2ea3479ac3d/go.mod h1:0lTi9P0dEYoOx+d6vBoXfNrWYbj5RR1hYLTCyXE4d5E=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
Expand Down
1 change: 1 addition & 0 deletions internal/commands/buildpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func NewBuildpackCommand(logger logging.Logger, cfg config.Config, client PackCl
cmd.AddCommand(BuildpackInspect(logger, cfg, client))
cmd.AddCommand(BuildpackPackage(logger, cfg, client, packageConfigReader))
cmd.AddCommand(BuildpackNew(logger, client))
cmd.AddCommand(BuildpackCreate(logger, client))
cmd.AddCommand(BuildpackPull(logger, cfg, client))
cmd.AddCommand(BuildpackRegister(logger, cfg, client))
cmd.AddCommand(BuildpackYank(logger, cfg, client))
Expand Down
70 changes: 70 additions & 0 deletions internal/commands/buildpack_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package commands

import (
"context"

scafall "github.com/buildpacks/scafall/pkg"
"github.com/spf13/cobra"

"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/pkg/client"
"github.com/buildpacks/pack/pkg/logging"
)

const (
CNBTemplates = "https://github.com/buildpacks/templates"
)

type BuildpackCreateFlags struct {
Arguments map[string]string
Template string
SubPath string
}

type BuildpackCreateCreator interface {
CreateBuildpack(ctx context.Context, options client.CreateBuildpackOptions) error
}

func BuildpackCreate(logger logging.Logger, creator BuildpackCreateCreator) *cobra.Command {
flags := BuildpackCreateFlags{}
cmd := &cobra.Command{
Use: "create",
Short: "Creates basic scaffolding of a buildpack.",
Args: cobra.MatchAll(cobra.ExactArgs(0)),
Example: "pack buildpack create",
Long: "buildpack new generates the basic scaffolding of a buildpack repository.",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
if err := creator.CreateBuildpack(cmd.Context(), client.CreateBuildpackOptions{
Template: flags.Template,
SubPath: flags.SubPath,
Arguments: flags.Arguments,
}); err != nil {
return err
}

logger.Infof("Successfully scaffolded %s", style.Symbol("template"))
return nil
}),
}

cmd.Flags().StringVarP(&flags.Template, "template", "t", CNBTemplates, "URL of the buildpack template git repository")
cmd.Flags().StringVar(&flags.SubPath, "sub-path", "", "directory within template git repository used to generate the buildpack")
cmd.Flags().StringToStringVarP(&flags.Arguments, "arg", "a", nil, "arguments to the buildpack template")

cmd.SetHelpFunc(func(*cobra.Command, []string) {
s, err := scafall.NewScafall(flags.Template, scafall.WithSubPath(flags.SubPath))
if err != nil {
logger.Errorf("unable to get help for template: %s", err)
} else {
info, args, err := s.TemplateArguments()
if err != nil {
logger.Errorf("unable to get template arguments for template: %s", err)
}
logger.Info(info)
for _, arg := range args {
logger.Infof("\t%s", arg)
}
}
})
return cmd
}
70 changes: 70 additions & 0 deletions internal/commands/buildpack_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package commands_test

import (
"bytes"
"os"
"testing"

"github.com/golang/mock/gomock"
"github.com/heroku/color"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/spf13/cobra"

"github.com/buildpacks/pack/internal/commands"
"github.com/buildpacks/pack/internal/commands/testmocks"
"github.com/buildpacks/pack/pkg/client"
"github.com/buildpacks/pack/pkg/logging"
h "github.com/buildpacks/pack/testhelpers"
)

func TestBuildpackCreateCommand(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "BuildpackCreateCommand", testBuildpackCreateCommand, spec.Parallel(), spec.Report(report.Terminal{}))
}

func testBuildpackCreateCommand(t *testing.T, when spec.G, it spec.S) {
var (
command *cobra.Command
logger *logging.LogWithWriters
outBuf bytes.Buffer
mockController *gomock.Controller
mockClient *testmocks.MockPackClient
tmpDir string
)

it.Before(func() {
tmpDir = t.TempDir()
logger = logging.NewLogWithWriters(&outBuf, &outBuf)
mockController = gomock.NewController(t)
mockClient = testmocks.NewMockPackClient(mockController)

command = commands.BuildpackCreate(logger, mockClient)
})

it.After(func() {
os.RemoveAll(tmpDir)
})

when("BuildpackCreate#Execute", func() {
it("uses the args to generate artifacts", func() {
mockClient.EXPECT().CreateBuildpack(gomock.Any(), client.CreateBuildpackOptions{
Template: "testdata",
SubPath: "create",
Arguments: map[string]string{
"Test": "Quack",
},
}).Return(nil).MaxTimes(1)

command.SetArgs([]string{
"--template", "testdata",
"--sub-path", "create",
"--arg", "Test=Quack",
})

err := command.Execute()
h.AssertNil(t, err)
})
})
}
4 changes: 4 additions & 0 deletions internal/commands/buildpack_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ type BuildpackNewFlags struct {

// BuildpackCreator creates buildpacks
type BuildpackCreator interface {
// Deprecated: use CreateBuildpack instead
NewBuildpack(ctx context.Context, options client.NewBuildpackOptions) error
}

// BuildpackNew generates the scaffolding of a buildpack
//
// Deprecated: Use BuildpackCreate instead
func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Command {
var flags BuildpackNewFlags
cmd := &cobra.Command{
Expand All @@ -38,6 +41,7 @@ func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Comman
Example: "pack buildpack new sample/my-buildpack",
Long: "buildpack new generates the basic scaffolding of a buildpack repository. It creates a new directory `name` in the current directory (or at `path`, if passed as a flag), and initializes a buildpack.toml, and two executable bash scripts, `bin/detect` and `bin/build`. ",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
logger.Warn("pack buildpack new is deprecated, prefer pack buildpack create instead")
id := args[0]
idParts := strings.Split(id, "/")
dirName := idParts[len(idParts)-1]
Expand Down
1 change: 1 addition & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type PackClient interface {
Rebase(context.Context, client.RebaseOptions) error
CreateBuilder(context.Context, client.CreateBuilderOptions) error
NewBuildpack(context.Context, client.NewBuildpackOptions) error
CreateBuildpack(context.Context, client.CreateBuildpackOptions) error
PackageBuildpack(ctx context.Context, opts client.PackageBuildpackOptions) error
Build(context.Context, client.BuildOptions) error
RegisterBuildpack(context.Context, client.RegisterBuildpackOptions) error
Expand Down
14 changes: 14 additions & 0 deletions internal/commands/testmocks/mock_pack_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions pkg/client/create_buildpack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package client

import (
"context"

scafall "github.com/buildpacks/scafall/pkg"
)

type CreateBuildpackOptions struct {
// URL of git repository containing the project template
Template string

// Subdirectory within the repository containing the project template
SubPath string

// arguments to provide to the project template
Arguments map[string]string
}

func (c *Client) CreateBuildpack(ctx context.Context, opts CreateBuildpackOptions) error {
ops := []scafall.Option{scafall.WithSubPath(opts.SubPath)}
if opts.Arguments != nil {
ops = append(ops, scafall.WithArguments(opts.Arguments))
}
s, err := scafall.NewScafall(opts.Template, ops...)
if err != nil {
return err
}
err = s.Scaffold()
if err != nil {
return err
}
return nil
}
1 change: 1 addition & 0 deletions pkg/client/new_buildpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type NewBuildpackOptions struct {
Stacks []dist.Stack
}

// Deprecated: use CreateBuildpack instead
func (c *Client) NewBuildpack(ctx context.Context, opts NewBuildpackOptions) error {
err := createBuildpackTOML(opts.Path, opts.ID, opts.Version, opts.API, opts.Stacks, c)
if err != nil {
Expand Down

0 comments on commit 6e320e4

Please sign in to comment.