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

added targets flag for buildpack new cli #1921

Merged
merged 14 commits into from
Oct 17, 2023
146 changes: 143 additions & 3 deletions internal/commands/buildpack_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
"fmt"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/spf13/cobra"

"github.com/pkg/errors"

"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/pkg/client"
"github.com/buildpacks/pack/pkg/dist"
Expand All @@ -17,9 +20,11 @@

// BuildpackNewFlags define flags provided to the BuildpackNew command
type BuildpackNewFlags struct {
API string
Path string
API string
Path string
// Deprecated: Stacks are deprecated
Stacks []string
Targets []string
Version string
}

Expand Down Expand Up @@ -66,11 +71,46 @@
})
}

var targets []dist.Target
for _, t := range flags.Targets {
var distroMap []dist.Distribution
target, nonDistro, distros, err := getTarget(t)
if err != nil {
logger.Error(err.Error())
}

Check warning on line 80 in internal/commands/buildpack_new.go

View check run for this annotation

Codecov / codecov/patch

internal/commands/buildpack_new.go#L79-L80

Added lines #L79 - L80 were not covered by tests
if i, e := getSliceAt[string](target, 1); e == nil {
distros = strings.Split(i, ";")
}
for _, d := range distros {
distro := strings.Split(d, "@")
if l := len(distro); l <= 0 {
return errors.Errorf("distro is nil!")

Check warning on line 87 in internal/commands/buildpack_new.go

View check run for this annotation

Codecov / codecov/patch

internal/commands/buildpack_new.go#L87

Added line #L87 was not covered by tests
} else if l == 1 {
logger.Warnf("forgot to specify version for distro %s ?", distro[0])
}

Check warning on line 90 in internal/commands/buildpack_new.go

View check run for this annotation

Codecov / codecov/patch

internal/commands/buildpack_new.go#L89-L90

Added lines #L89 - L90 were not covered by tests
distroMap = append(distroMap, dist.Distribution{
Name: distro[0],
Versions: distro[1:],
})
}
os, arch, variant, err := getPlatform(nonDistro)
if err != nil {
return err
}
targets = append(targets, dist.Target{
OS: os,
Arch: arch,
ArchVariant: variant,
Distributions: distroMap,
})
}
WYGIN marked this conversation as resolved.
Show resolved Hide resolved

if err := creator.NewBuildpack(cmd.Context(), client.NewBuildpackOptions{
API: flags.API,
ID: id,
Path: path,
Stacks: stacks,
Targets: targets,
Version: flags.Version,
}); err != nil {
return err
Expand All @@ -84,8 +124,108 @@
cmd.Flags().StringVarP(&flags.API, "api", "a", "0.8", "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.jammy"}, "Stack(s) this buildpack will be compatible with"+stringSliceHelp("stack"))
cmd.Flags().StringSliceVarP(&flags.Stacks, "stacks", "s", []string{}, "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")
cmd.Flags().StringSliceVarP(&flags.Targets, "targets", "t", []string{runtime.GOOS + "/" + runtime.GOARCH},
`Targets are the list platforms that one targeting, these are generated as part of scaffolding inside buildpack.toml file. one can provide target platforms in format [os][/arch][/variant]:[distroname@osversion@anotherversion];[distroname@osversion]
- Base case for two different architectures : '--targets "linux/amd64" --targets "linux/arm64"'
- case for distribution version: '--targets "windows/amd64:[email protected]"'
- case for different architecture with distributed versions : '--targets "linux/arm/v6:[email protected]" --targets "linux/arm/v6:[email protected]"'
`)

AddHelpFlag(cmd, "new")
return cmd
}

func getSliceAt[T interface{}](slice []T, index int) (T, error) {
if index < 0 || index >= len(slice) {
var r T
return r, errors.Errorf("index out of bound, cannot access item at index %d of slice with length %d", index, len(slice))
}

return slice[index], nil
}

var GOOSArch = map[string][]string{
"aix": {"ppc64"},
"android": {"386", "amd64", "arm", "arm64"},
"darwin": {"amd64", "arm64"},
"dragonfly": {"amd64"},
"freebsd": {"386", "amd64", "arm"},
"illumos": {"amd64"},
"ios": {"arm64"},
"js": {"wasm"},
"linux": {"386", "amd64", "arm", "arm64", "loong64", "mips", "mipsle", "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x"},
"netbsd": {"386", "amd64", "arm"},
"openbsd": {"386", "amd64", "arm", "arm64"},
"plan9": {"386", "amd64", "arm"},
"solaris": {"amd64"},
"wasip1": {"wasm"},
"windows": {"386", "amd64", "arm", "arm64"},
}

var GOArchVariant = map[string][]string{
"386": {"softfloat", "sse2"},
"arm": {"v5", "v6", "v7"},
"amd64": {"v1", "v2", "v3", "v4"},
"mips": {"hardfloat", "softfloat"},
"mipsle": {"hardfloat", "softfloat"},
"mips64": {"hardfloat", "softfloat"},
"mips64le": {"hardfloat", "softfloat"},
"ppc64": {"power8", "power9"},
"ppc64le": {"power8", "power9"},
"wasm": {"satconv", "signext"},
}

func isOS(os string) bool {
return GOOSArch[os] != nil
}

func supportsArch(os string, arch string) bool {
if isOS(os) {
var supported bool
for _, s := range GOOSArch[os] {
if s == arch {
supported = true
break
}
}
return supported
}
return false

Check warning on line 195 in internal/commands/buildpack_new.go

View check run for this annotation

Codecov / codecov/patch

internal/commands/buildpack_new.go#L195

Added line #L195 was not covered by tests
}

func supportsVariant(arch string, variant string) bool {
if variant == "" || len(variant) == 0 {
return true
}
var supported bool
for _, s := range GOArchVariant[arch] {
if s == variant {
supported = true
break
}
}
return supported
}

func getTarget(t string) ([]string, []string, []string, error) {
var nonDistro, distro []string
target := strings.Split(t, ":")
if i, e := getSliceAt[string](target, 0); e != nil {
return target, nonDistro, distro, errors.Errorf("invalid target %s, atleast one of [os][/arch][/archVariant] must be specified", t)

Check warning on line 216 in internal/commands/buildpack_new.go

View check run for this annotation

Codecov / codecov/patch

internal/commands/buildpack_new.go#L216

Added line #L216 was not covered by tests
} else {
nonDistro = strings.Split(i, "/")
}
return target, nonDistro, distro, nil
}

func getPlatform(t []string) (string, string, string, error) {
os, _ := getSliceAt[string](t, 0)
arch, _ := getSliceAt[string](t, 1)
variant, _ := getSliceAt[string](t, 2)
if !isOS(os) || !supportsArch(os, arch) || !supportsVariant(arch, variant) {
return os, arch, variant, errors.Errorf("unknown target: %s", style.Symbol(strings.Join(t, "/")))
}
return os, arch, variant, nil
}
127 changes: 124 additions & 3 deletions internal/commands/buildpack_new_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/buildpacks/pack/pkg/client"
Expand Down Expand Up @@ -60,9 +61,9 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) {
ID: "example/some-cnb",
Path: filepath.Join(tmpDir, "some-cnb"),
Version: "1.0.0",
Stacks: []dist.Stack{{
ID: "io.buildpacks.stacks.jammy",
Mixins: []string{},
Targets: []dist.Target{{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}},
}).Return(nil).MaxTimes(1)

Expand All @@ -82,5 +83,125 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) {
h.AssertNotNil(t, err)
h.AssertContains(t, outBuf.String(), "ERROR: directory")
})

when("target flag is specified, ", func() {
it("it uses target to generate artifacts", func() {
mockClient.EXPECT().NewBuildpack(gomock.Any(), client.NewBuildpackOptions{
API: "0.8",
ID: "example/targets",
Path: filepath.Join(tmpDir, "targets"),
Version: "1.0.0",
Targets: []dist.Target{{
OS: "linux",
Arch: "arm",
ArchVariant: "v6",
Distributions: []dist.Distribution{{
Name: "ubuntu",
Versions: []string{"14.04", "16.04"},
}},
}},
}).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "targets")
command.SetArgs([]string{"--path", path, "example/targets", "--targets", "linux/arm/v6:[email protected]@16.04"})

err := command.Execute()
h.AssertNil(t, err)
})
it("it should show error when invalid [os]/[arch] passed", func() {
mockClient.EXPECT().NewBuildpack(gomock.Any(), client.NewBuildpackOptions{
API: "0.8",
ID: "example/targets",
Path: filepath.Join(tmpDir, "targets"),
Version: "1.0.0",
Targets: []dist.Target{{
OS: "os",
Arch: "arm",
ArchVariant: "v6",
Distributions: []dist.Distribution{{
Name: "ubuntu",
Versions: []string{"14.04", "16.04"},
}},
}},
}).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "targets")
command.SetArgs([]string{"--path", path, "example/targets", "--targets", "os/arm/v6:[email protected]@16.04"})

err := command.Execute()
h.AssertNotNil(t, err)
})
when("it should", func() {
it("support format [os][/arch][/variant]:[name@version@version2];[some-name@version@version2]", func() {
mockClient.EXPECT().NewBuildpack(gomock.Any(), client.NewBuildpackOptions{
API: "0.8",
ID: "example/targets",
Path: filepath.Join(tmpDir, "targets"),
Version: "1.0.0",
Targets: []dist.Target{
{
OS: "linux",
Arch: "arm",
ArchVariant: "v6",
Distributions: []dist.Distribution{
{
Name: "ubuntu",
Versions: []string{"14.04", "16.04"},
},
{
Name: "debian",
Versions: []string{"8.10", "10.9"},
},
},
},
{
OS: "windows",
Arch: "amd64",
Distributions: []dist.Distribution{
{
Name: "windows-nano",
Versions: []string{"10.0.19041.1415"},
},
},
},
},
}).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "targets")
command.SetArgs([]string{"--path", path, "example/targets", "--targets", "linux/arm/v6:[email protected]@16.04;[email protected]@10.9", "-t", "windows/amd64:[email protected]"})

err := command.Execute()
h.AssertNil(t, err)
})
})
when("stacks ", func() {
it("flag should show deprecated message when used", func() {
mockClient.EXPECT().NewBuildpack(gomock.Any(), client.NewBuildpackOptions{
API: "0.8",
ID: "example/stacks",
Path: filepath.Join(tmpDir, "stacks"),
Version: "1.0.0",
Stacks: []dist.Stack{{
ID: "io.buildpacks.stacks.jammy",
Mixins: []string{},
}},
Targets: []dist.Target{{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}},
}).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "stacks")
output := new(bytes.Buffer)
command.SetOut(output)
command.SetErr(output)
command.SetArgs([]string{"--path", path, "example/stacks", "--stacks", "io.buildpacks.stacks.jammy"})

err := command.Execute()
h.AssertNil(t, err)
h.AssertContains(t, output.String(), "Flag --stacks has been deprecated,")
})
})
})
})
}
2 changes: 1 addition & 1 deletion pkg/client/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,7 @@ func createInlineBuildpack(bp projectTypes.Buildpack, stackID string) (string, e
bp.Version = "0.0.0"
}

if err = createBuildpackTOML(pathToInlineBuilpack, bp.ID, bp.Version, bp.Script.API, []dist.Stack{{ID: stackID}}, nil); err != nil {
if err = createBuildpackTOML(pathToInlineBuilpack, bp.ID, bp.Version, bp.Script.API, []dist.Stack{{ID: stackID}}, []dist.Target{}, nil); err != nil {
return pathToInlineBuilpack, err
}

Expand Down
14 changes: 9 additions & 5 deletions pkg/client/new_buildpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ type NewBuildpackOptions struct {
// version of the output buildpack artifact.
Version string

// The stacks this buildpack will work with
// Deprecated: The stacks this buildpack will work with
Stacks []dist.Stack

// the targets this buildpack will work with
Targets []dist.Target
}

func (c *Client) NewBuildpack(ctx context.Context, opts NewBuildpackOptions) error {
err := createBuildpackTOML(opts.Path, opts.ID, opts.Version, opts.API, opts.Stacks, c)
err := createBuildpackTOML(opts.Path, opts.ID, opts.Version, opts.API, opts.Stacks, opts.Targets, c)
if err != nil {
return err
}
Expand Down Expand Up @@ -94,15 +97,16 @@ func createBinScript(path, name, contents string, c *Client) error {
return nil
}

func createBuildpackTOML(path, id, version, apiStr string, stacks []dist.Stack, c *Client) error {
func createBuildpackTOML(path, id, version, apiStr string, stacks []dist.Stack, targets []dist.Target, c *Client) error {
api, err := api.NewVersion(apiStr)
if err != nil {
return err
}

buildpackTOML := dist.BuildpackDescriptor{
WithAPI: api,
WithStacks: stacks,
WithAPI: api,
WithStacks: stacks,
WithTargets: targets,
WithInfo: dist.ModuleInfo{
ID: id,
Version: version,
Expand Down
1 change: 1 addition & 0 deletions pkg/dist/buildmodule.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Stack struct {
type Target struct {
OS string `json:"os" toml:"os"`
Arch string `json:"arch" toml:"arch"`
ArchVariant string `json:"variant,omitempty" toml:"variant,omitempty"`
Distributions []Distribution `json:"distributions,omitempty" toml:"distributions,omitempty"`
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/dist/buildpack_descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
type BuildpackDescriptor struct {
WithAPI *api.Version `toml:"api"`
WithInfo ModuleInfo `toml:"buildpack"`
WithStacks []Stack `toml:"stacks"`
WithStacks []Stack `toml:"stacks,omitempty"`
WithTargets []Target `toml:"targets,omitempty"`
WithOrder Order `toml:"order"`
WithWindowsBuild bool
Expand Down
Loading