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
30 changes: 27 additions & 3 deletions internal/commands/buildpack_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/spf13/cobra"

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

// 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 +70,24 @@ func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Comman
})
}

var targets []dist.Target
if len(flags.Targets) == 0 && len(flags.Stacks) == 0 {
targets = []dist.Target{{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}}
} else {
if targets, err = target.ParseTargets(flags.Targets, logger); err != nil {
return err
}
}

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,7 +101,14 @@ func BuildpackNew(logger logging.Logger, creator BuildpackCreator) *cobra.Comman
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", 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")
cmd.Flags().StringSliceVarP(&flags.Targets, "targets", "t", nil,
`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
Expand Down
126 changes: 122 additions & 4 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 @@ -36,6 +37,10 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) {
mockClient *testmocks.MockPackClient
tmpDir string
)
targets := []dist.Target{{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}}

it.Before(func() {
var err error
Expand All @@ -60,10 +65,7 @@ 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: targets,
}).Return(nil).MaxTimes(1)

path := filepath.Join(tmpDir, "some-cnb")
Expand All @@ -82,5 +84,121 @@ 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{},
}},
}).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,")
})
})
})
})
}
101 changes: 101 additions & 0 deletions internal/target/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package target

import (
"strings"

"github.com/pkg/errors"

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

func ParseTargets(t []string, logger logging.Logger) (targets []dist.Target, err error) {
for _, v := range t {
target, err := ParseTarget(v, logger)
if err != nil {
return nil, err
}
targets = append(targets, target)
}
return targets, nil
}

func ParseTarget(t string, logger logging.Logger) (output dist.Target, err error) {
nonDistro, distros, err := getTarget(t, logger)
if v, _ := getSliceAt[string](nonDistro, 0); len(nonDistro) <= 1 && v == "" {
logger.Warn("os/arch must be defined")
}
if err != nil {
return output, err
}
os, arch, variant, err := getPlatform(nonDistro, logger)
if err != nil {
return output, err
}
v, err := ParseDistros(distros, logger)
if err != nil {
return output, err
}
output = dist.Target{
OS: os,
Arch: arch,
ArchVariant: variant,
Distributions: v,
}
return output, err
}

func ParseDistros(distroSlice string, logger logging.Logger) (distros []dist.Distribution, err error) {
distro := strings.Split(distroSlice, ";")
if l := len(distro); l == 1 && distro[0] == "" {
return nil, err
}
for _, d := range distro {
v, err := ParseDistro(d, logger)
if err != nil {
return nil, err
}
distros = append(distros, v)
}
return distros, nil
}

func ParseDistro(distroString string, logger logging.Logger) (distro dist.Distribution, err error) {
d := strings.Split(distroString, "@")
if d[0] == "" || len(d) == 0 {
return distro, errors.Errorf("distro's versions %s cannot be specified without distro's name", style.Symbol("@"+strings.Join(d[1:], "@")))
}
if len(d) <= 2 && (strings.Contains(strings.Join(d[1:], ""), "") || d[1] == "") {
logger.Warnf("distro with name %s has no specific version!", style.Symbol(d[0]))
}
distro.Name = d[0]
distro.Versions = d[1:]
return distro, err
}

func getTarget(t string, logger logging.Logger) (nonDistro []string, distros string, err error) {
target := strings.Split(t, ":")
if (len(target) == 1 && target[0] == "") || len(target) == 0 {
return nonDistro, distros, errors.Errorf("invalid target %s, atleast one of [os][/arch][/archVariant] must be specified", t)
}
if len(target) == 2 && target[0] == "" {
v, _ := getSliceAt[string](target, 1)
logger.Warn(style.Warn("adding distros %s without [os][/arch][/variant]", v))
} else {
i, _ := getSliceAt[string](target, 0)
nonDistro = strings.Split(i, "/")
}
if i, err := getSliceAt[string](target, 1); err == nil {
distros = i
}
return nonDistro, distros, err
}

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

return slice[index], err
}
Loading
Loading