-
Notifications
You must be signed in to change notification settings - Fork 294
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1025 from buildpacks/jkutner/new-buildpack-toml-keys
Add buildpack create command to generate new buildpack scaffolding
- Loading branch information
Showing
10 changed files
with
457 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package commands | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/buildpacks/pack" | ||
"github.com/buildpacks/pack/internal/build" | ||
"github.com/buildpacks/pack/internal/dist" | ||
"github.com/buildpacks/pack/internal/style" | ||
"github.com/buildpacks/pack/logging" | ||
) | ||
|
||
// BuildpackNewFlags define flags provided to the BuildpackNew command | ||
type BuildpackNewFlags struct { | ||
API string | ||
Path string | ||
Stacks []string | ||
Version string | ||
} | ||
|
||
// BuildpackCreator creates buildpacks | ||
type BuildpackCreator interface { | ||
NewBuildpack(ctx context.Context, options pack.NewBuildpackOptions) error | ||
} | ||
|
||
// BuildpackNew generates the scaffolding of a buildpack | ||
func BuildpackNew(logger logging.Logger, client BuildpackCreator) *cobra.Command { | ||
var flags BuildpackNewFlags | ||
cmd := &cobra.Command{ | ||
Use: "new <id>", | ||
Short: "Creates basic scaffolding of a buildpack.", | ||
Args: cobra.ExactValidArgs(1), | ||
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 { | ||
id := args[0] | ||
idParts := strings.Split(id, "/") | ||
dirName := idParts[len(idParts)-1] | ||
|
||
var path string | ||
if len(flags.Path) == 0 { | ||
cwd, err := os.Getwd() | ||
if err != nil { | ||
return err | ||
} | ||
path = filepath.Join(cwd, dirName) | ||
} else { | ||
path = flags.Path | ||
} | ||
|
||
_, err := os.Stat(path) | ||
if !os.IsNotExist(err) { | ||
return fmt.Errorf("directory %s exists", style.Symbol(path)) | ||
} | ||
|
||
var stacks []dist.Stack | ||
for _, s := range flags.Stacks { | ||
stacks = append(stacks, dist.Stack{ | ||
ID: s, | ||
Mixins: []string{}, | ||
}) | ||
} | ||
|
||
if err := client.NewBuildpack(cmd.Context(), pack.NewBuildpackOptions{ | ||
API: flags.API, | ||
ID: id, | ||
Path: path, | ||
Stacks: stacks, | ||
Version: flags.Version, | ||
}); err != nil { | ||
return err | ||
} | ||
|
||
logger.Infof("Successfully created %s", style.Symbol(id)) | ||
return nil | ||
}), | ||
} | ||
|
||
cmd.Flags().StringVarP(&flags.API, "api", "a", build.SupportedPlatformAPIVersions.Latest().String(), "Buildpack API compatibility of the generated buildpack") | ||
cmd.Flags().StringVarP(&flags.Path, "path", "p", "", "Path to generate the buildpack") | ||
cmd.Flags().StringVarP(&flags.Version, "version", "V", "1.0.0", "Version of the generated buildpack") | ||
cmd.Flags().StringSliceVarP(&flags.Stacks, "stacks", "s", []string{"io.buildpacks.stacks.bionic"}, "Stack(s) this buildpack will be compatible with"+multiValueHelp("stack")) | ||
|
||
AddHelpFlag(cmd, "new") | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package commands_test | ||
|
||
import ( | ||
"bytes" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/buildpacks/pack" | ||
"github.com/buildpacks/pack/internal/dist" | ||
|
||
"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" | ||
ilogging "github.com/buildpacks/pack/internal/logging" | ||
h "github.com/buildpacks/pack/testhelpers" | ||
) | ||
|
||
func TestBuildpackNewCommand(t *testing.T) { | ||
color.Disable(true) | ||
defer color.Disable(false) | ||
spec.Run(t, "BuildpackNewCommand", testBuildpackNewCommand, spec.Parallel(), spec.Report(report.Terminal{})) | ||
} | ||
|
||
func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { | ||
var ( | ||
command *cobra.Command | ||
logger *ilogging.LogWithWriters | ||
outBuf bytes.Buffer | ||
mockController *gomock.Controller | ||
mockClient *testmocks.MockPackClient | ||
tmpDir string | ||
) | ||
|
||
it.Before(func() { | ||
var err error | ||
tmpDir, err = ioutil.TempDir("", "build-test") | ||
h.AssertNil(t, err) | ||
|
||
logger = ilogging.NewLogWithWriters(&outBuf, &outBuf) | ||
mockController = gomock.NewController(t) | ||
mockClient = testmocks.NewMockPackClient(mockController) | ||
|
||
command = commands.BuildpackNew(logger, mockClient) | ||
}) | ||
|
||
it.After(func() { | ||
os.RemoveAll(tmpDir) | ||
}) | ||
|
||
when("BuildpackNew#Execute", func() { | ||
it("uses the args to generate artifacts", func() { | ||
mockClient.EXPECT().NewBuildpack(gomock.Any(), pack.NewBuildpackOptions{ | ||
API: "0.4", | ||
ID: "example/some-cnb", | ||
Path: filepath.Join(tmpDir, "some-cnb"), | ||
Version: "1.0.0", | ||
Stacks: []dist.Stack{{ | ||
ID: "io.buildpacks.stacks.bionic", | ||
Mixins: []string{}, | ||
}}, | ||
}).Return(nil).MaxTimes(1) | ||
|
||
path := filepath.Join(tmpDir, "some-cnb") | ||
command.SetArgs([]string{"--path", path, "example/some-cnb"}) | ||
|
||
err := command.Execute() | ||
h.AssertNil(t, err) | ||
}) | ||
|
||
it("stops if the directory already exists", func() { | ||
err := os.MkdirAll(tmpDir, 0600) | ||
h.AssertNil(t, err) | ||
|
||
command.SetArgs([]string{"--path", tmpDir, "example/some-cnb"}) | ||
err = command.Execute() | ||
h.AssertNotNil(t, err) | ||
h.AssertContains(t, outBuf.String(), "ERROR: directory") | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
internal/commands/testmocks/mock_inspect_image_writer_factory.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package pack | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/BurntSushi/toml" | ||
|
||
"github.com/buildpacks/lifecycle/api" | ||
|
||
"github.com/buildpacks/pack/internal/dist" | ||
"github.com/buildpacks/pack/internal/style" | ||
) | ||
|
||
var ( | ||
bashBinBuild = ` | ||
#!/usr/bin/env bash | ||
set -euo pipefail | ||
layers_dir="$1" | ||
env_dir="$2/env" | ||
plan_path="$3" | ||
exit 0 | ||
` | ||
bashBinDetect = ` | ||
#!/usr/bin/env bash | ||
exit 0 | ||
` | ||
) | ||
|
||
type NewBuildpackOptions struct { | ||
// api compat version of the output buildpack artifact. | ||
API string | ||
|
||
// The base directory to generate assets | ||
Path string | ||
|
||
// The ID of the output buildpack artifact. | ||
ID string | ||
|
||
// version of the output buildpack artifact. | ||
Version string | ||
|
||
// The stacks this buildpack will work with | ||
Stacks []dist.Stack | ||
} | ||
|
||
func (c *Client) NewBuildpack(ctx context.Context, opts NewBuildpackOptions) error { | ||
api, err := api.NewVersion(opts.API) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
buildpackTOML := dist.BuildpackDescriptor{ | ||
API: api, | ||
Stacks: opts.Stacks, | ||
Info: dist.BuildpackInfo{ | ||
ID: opts.ID, | ||
Version: opts.Version, | ||
}, | ||
} | ||
|
||
if err := os.MkdirAll(opts.Path, 0755); err != nil { | ||
return err | ||
} | ||
|
||
buildpackTOMLPath := filepath.Join(opts.Path, "buildpack.toml") | ||
_, err = os.Stat(buildpackTOMLPath) | ||
if os.IsNotExist(err) { | ||
f, err := os.Create(buildpackTOMLPath) | ||
if err != nil { | ||
return err | ||
} | ||
if err := toml.NewEncoder(f).Encode(buildpackTOML); err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
c.logger.Infof(" %s buildpack.toml", style.Symbol("create")) | ||
} | ||
|
||
return createBashBuildpack(opts.Path, c) | ||
} | ||
|
||
func createBashBuildpack(path string, c *Client) error { | ||
if err := createBinScript(path, "build", bashBinBuild, c); err != nil { | ||
return err | ||
} | ||
|
||
if err := createBinScript(path, "detect", bashBinDetect, c); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createBinScript(path, name, contents string, c *Client) error { | ||
binDir := filepath.Join(path, "bin") | ||
binFile := filepath.Join(binDir, name) | ||
|
||
_, err := os.Stat(binFile) | ||
if os.IsNotExist(err) { | ||
if err := os.MkdirAll(binDir, 0755); err != nil { | ||
return err | ||
} | ||
|
||
err = ioutil.WriteFile(binFile, []byte(contents), 0755) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c.logger.Infof(" %s bin/%s", style.Symbol("create"), name) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.