Skip to content

Commit

Permalink
added tests & show error msg when unknow target is passed
Browse files Browse the repository at this point in the history
Signed-off-by: WYGIN <[email protected]>
  • Loading branch information
WYGIN committed Sep 28, 2023
1 parent 16b8350 commit e79226e
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 26 deletions.
112 changes: 96 additions & 16 deletions internal/commands/buildpack_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -73,23 +74,17 @@ func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Comman
var targets []dist.Target
for _, t := range flags.Targets {
var distroMap []dist.Distribution
var nonDistro []string
var distros []string
target := strings.Split(t, ":")
if i, e := getSliceAt[string](target, 0); e != nil {
logger.Errorf("invalid target %s, atleast one of [os][/arch][/archVariant] must be specified", t)
} else {
nonDistro = strings.Split(i, "/")
target, nonDistro, distros, err := getTarget(t)
if err != nil {
logger.Error(err.Error())
}
if i, e := getSliceAt[string](target, 1); e != nil {
logger.Errorf("invalid target %s, atleast one of [name][version] must be specified", t)
} else {
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 {
logger.Error("distro is nil!")
return errors.Errorf("distro is nil!")
} else if l == 1 {
logger.Warnf("forgot to specify version for distro %s ?", distro[0])
}
Expand All @@ -98,9 +93,10 @@ func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Comman
Versions: distro[1:],
})
}
os, _ := getSliceAt[string](nonDistro, 0)
arch, _ := getSliceAt[string](nonDistro, 1)
variant, _ := getSliceAt[string](nonDistro, 2)
os, arch, variant, err := getPlatform(nonDistro)
if err != nil {
logger.Error(err.Error())
}
targets = append(targets, dist.Target{
OS: os,
Arch: arch,
Expand Down Expand Up @@ -129,8 +125,8 @@ func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Comman
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{}, "Stack(s) this buildpack will be compatible with"+stringSliceHelp("stack"))
cmd.Flags().MarkDeprecated("stacks", "")
cmd.Flags().StringSliceVarP(&flags.Targets, "targets", "t", []string{"/"},
cmd.Flags().MarkDeprecated("stacks", "prefer `--targets` instead: https://github.com/buildpacks/rfcs/blob/main/text/0096-remove-stacks-mixins.md")
cmd.PersistentFlags().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]"'
Expand All @@ -149,3 +145,87 @@ func getSliceAt[T interface{}](slice []T, index int) (T, error) {

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
}

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)
} 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
}
168 changes: 158 additions & 10 deletions internal/commands/buildpack_new_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,10 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) {
ID: "io.buildpacks.stacks.jammy",
Mixins: []string{},
}},
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, "some-cnb")
command.SetArgs([]string{"--path", path, "example/some-cnb"})
command.SetArgs([]string{"--path", path, "example/some-cnb", "--stacks", "io.buildpacks.stacks.jammy"})

err := command.Execute()
h.AssertNil(t, err)
Expand All @@ -91,5 +82,162 @@ 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)
})

it("generate a buildpack.toml file with os and arch as empty strings when flag is not specified", 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: "",
Arch: "",
}},
}).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "targets")
command.SetArgs([]string{"--path", path, "example/targets"})

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{},
}},
}).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,")
})

it("should be omitted from buildpack.toml file when flag is not specified", func() {
options := client.NewBuildpackOptions{
API: "0.8",
ID: "example/stacks",
Path: filepath.Join(tmpDir, "stacks"),
Version: "1.0.0",
}

mockClient.EXPECT().NewBuildpack(gomock.Any(), options).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "stacks")
tomlFile := filepath.Join(path, "buildpack.toml")
command.SetArgs([]string{"--path", path, "example/stacks"})

err := command.Execute()
h.AssertNil(t, err)
output, err := os.ReadFile(tomlFile)
h.AssertNil(t, err)
h.AssertNotContains(t, string(output), "[[stacks]]")
})
})
})
})
}

0 comments on commit e79226e

Please sign in to comment.