Skip to content

Commit

Permalink
Merge pull request #1921 from WYGIN/buildpack-new-targets-flag
Browse files Browse the repository at this point in the history
added targets flag for buildpack new cli
  • Loading branch information
jkutner authored Oct 17, 2023
2 parents 368515e + d84b81f commit a2b862c
Show file tree
Hide file tree
Showing 10 changed files with 526 additions and 14 deletions.
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

0 comments on commit a2b862c

Please sign in to comment.