Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pack): add support for scaffolding new extensions #2026

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type PackClient interface {
NewBuildpack(context.Context, client.NewBuildpackOptions) error
PackageBuildpack(ctx context.Context, opts client.PackageBuildpackOptions) error
PackageExtension(ctx context.Context, opts client.PackageBuildpackOptions) error
NewExtension(ctx context.Context, options client.NewExtensionOptions) error
Build(context.Context, client.BuildOptions) error
RegisterBuildpack(context.Context, client.RegisterBuildpackOptions) error
YankBuildpack(client.YankBuildpackOptions) error
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func NewExtensionCommand(logger logging.Logger, cfg config.Config, client PackCl
// client and packageConfigReader to be passed later on
cmd.AddCommand(ExtensionPackage(logger, cfg, client, packageConfigReader))
// client to be passed later on
cmd.AddCommand(ExtensionNew(logger))
cmd.AddCommand(ExtensionNew(logger, client))
cmd.AddCommand(ExtensionPull(logger, cfg, client))
cmd.AddCommand(ExtensionRegister(logger, cfg, client))
cmd.AddCommand(ExtensionYank(logger, cfg, client))
Expand Down
62 changes: 58 additions & 4 deletions internal/commands/extension_new.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package commands

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"

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

Expand All @@ -15,21 +24,66 @@ type ExtensionNewFlags struct {
}

// extensioncreator type to be added here and argument also to be added in the function
type ExtensionCreator interface {
NewExtension(ctx context.Context, options client.NewExtensionOptions) error
}

// ExtensionNew generates the scaffolding of an extension
func ExtensionNew(logger logging.Logger) *cobra.Command {
func ExtensionNew(logger logging.Logger, creator ExtensionCreator) *cobra.Command {
var flags ExtensionNewFlags
cmd := &cobra.Command{
Use: "new <id>",
Short: "Creates basic scaffolding of an extension",
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Example: "pack extension new <example-extension>",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
// logic will go here
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{},
})
}
sarthaksarthak9 marked this conversation as resolved.
Show resolved Hide resolved

if err := creator.NewExtension(cmd.Context(), client.NewExtensionOptions{
API: flags.API,
ID: id,
Path: path,
Stacks: stacks,
sarthaksarthak9 marked this conversation as resolved.
Show resolved Hide resolved
Version: flags.Version,
}); err != nil {
return err
}

logger.Infof("Successfully created %s", style.Symbol(id))
return nil
}),
}

// flags will go here
cmd.Flags().StringVarP(&flags.API, "api", "a", "0.8", "Extension API compatibility of the generated buildpack")
sarthaksarthak9 marked this conversation as resolved.
Show resolved Hide resolved
cmd.Flags().StringVarP(&flags.Path, "path", "p", "", "Path to generate the extension")
cmd.Flags().StringVarP(&flags.Version, "version", "V", "1.0.0", "Version of the generated extension")
cmd.Flags().StringSliceVarP(&flags.Stacks, "stacks", "s", nil, "Stack(s) this buildpack will be compatible with"+stringSliceHelp("stack"))
cmd.Flags().MarkDeprecated("stacks", "prefer `--targets` instead: https://github.com/buildpacks/rfcs/blob/main/text/0096-remove-stacks-mixins.md")
sarthaksarthak9 marked this conversation as resolved.
Show resolved Hide resolved

AddHelpFlag(cmd, "new")
return cmd
Expand Down
91 changes: 91 additions & 0 deletions pkg/client/new_extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package client

import (
"context"
"os"
"path/filepath"

"github.com/BurntSushi/toml"

"github.com/buildpacks/lifecycle/api"

"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/pkg/dist"
)

type NewExtensionOptions struct {
// api compat version of the output extension artifact.
API string

// The base directory to generate assets
Path string

// The ID of the output extension artifact.
ID string

// version of the output extension artifact.
Version string

// Deprecated: The stacks this extension will work with
Stacks []dist.Stack
sarthaksarthak9 marked this conversation as resolved.
Show resolved Hide resolved
}

func (c *Client) NewExtension(ctx context.Context, opts NewExtensionOptions) error {
err := createExtensionTOML(opts.Path, opts.ID, opts.Version, opts.API, c)
if err != nil {
return err
}
return createBashExtension(opts.Path, c)
}

func createBashExtension(path string, c *Client) error {
if err := createBinScript(path, "build", bashBinBuild, c); err != nil {
sarthaksarthak9 marked this conversation as resolved.
Show resolved Hide resolved
return err
}

if err := createBinScript(path, "detect", bashBinDetect, c); err != nil {
return err
}

return nil
}

func createExtensionTOML(path, id, version, apiStr string, c *Client) error {
api, err := api.NewVersion(apiStr)
if err != nil {
return err
}

extensionTOML := dist.ExtensionDescriptor{
WithAPI: api,
WithInfo: dist.ModuleInfo{
ID: id,
Version: version,
},
}

// The following line's comment is for gosec, it will ignore rule 301 in this case
// G301: Expect directory permissions to be 0750 or less
/* #nosec G301 */
if err := os.MkdirAll(path, 0755); err != nil {
return err
}

extensionTOMLPath := filepath.Join(path, "extension.toml")
_, err = os.Stat(extensionTOMLPath)
if os.IsNotExist(err) {
f, err := os.Create(extensionTOMLPath)
if err != nil {
return err
}
if err := toml.NewEncoder(f).Encode(extensionTOML); err != nil {
return err
}
defer f.Close()
if c != nil {
c.logger.Infof(" %s extension.toml", style.Symbol("create"))
}
}

return nil
}
Loading