mirror of https://github.com/docker/buildx.git
commit
7c91f3d0dd
151
commands/ls.go
151
commands/ls.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/platforms"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
@ -35,7 +36,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type lsOptions struct {
|
type lsOptions struct {
|
||||||
format string
|
format string
|
||||||
|
noTrunc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error {
|
func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error {
|
||||||
|
@ -72,7 +74,7 @@ func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasErrors, err := lsPrint(dockerCli, current, builders, in.format); err != nil {
|
if hasErrors, err := lsPrint(dockerCli, current, builders, in); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if hasErrors {
|
} else if hasErrors {
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
||||||
|
@ -107,6 +109,7 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
||||||
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
|
|
||||||
// hide builder persistent flag for this command
|
// hide builder persistent flag for this command
|
||||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
@ -114,14 +117,15 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, format string) (hasErrors bool, _ error) {
|
func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, in lsOptions) (hasErrors bool, _ error) {
|
||||||
if format == formatter.TableFormatKey {
|
if in.format == formatter.TableFormatKey {
|
||||||
format = lsDefaultTableFormat
|
in.format = lsDefaultTableFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := formatter.Context{
|
ctx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.Format(format),
|
Format: formatter.Format(in.format),
|
||||||
|
Trunc: !in.noTrunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(builders, func(i, j int) bool {
|
sort.SliceStable(builders, func(i, j int) bool {
|
||||||
|
@ -138,11 +142,12 @@ func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builde
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if err := format(&lsContext{
|
if err := format(&lsContext{
|
||||||
|
format: ctx.Format,
|
||||||
|
trunc: ctx.Trunc,
|
||||||
Builder: &lsBuilder{
|
Builder: &lsBuilder{
|
||||||
Builder: b,
|
Builder: b,
|
||||||
Current: b.Name == current.Name,
|
Current: b.Name == current.Name,
|
||||||
},
|
},
|
||||||
format: ctx.Format,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -160,6 +165,7 @@ func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builde
|
||||||
}
|
}
|
||||||
if err := format(&lsContext{
|
if err := format(&lsContext{
|
||||||
format: ctx.Format,
|
format: ctx.Format,
|
||||||
|
trunc: ctx.Trunc,
|
||||||
Builder: &lsBuilder{
|
Builder: &lsBuilder{
|
||||||
Builder: b,
|
Builder: b,
|
||||||
Current: b.Name == current.Name,
|
Current: b.Name == current.Name,
|
||||||
|
@ -196,6 +202,7 @@ type lsContext struct {
|
||||||
Builder *lsBuilder
|
Builder *lsBuilder
|
||||||
|
|
||||||
format formatter.Format
|
format formatter.Format
|
||||||
|
trunc bool
|
||||||
node builder.Node
|
node builder.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +268,11 @@ func (c *lsContext) Platforms() string {
|
||||||
if c.node.Name == "" {
|
if c.node.Name == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return strings.Join(platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms), ", ")
|
pfs := platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms)
|
||||||
|
if c.trunc && c.format.IsTable() {
|
||||||
|
return truncPlatforms(pfs, 4).String()
|
||||||
|
}
|
||||||
|
return strings.Join(pfs, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *lsContext) Error() string {
|
func (c *lsContext) Error() string {
|
||||||
|
@ -272,3 +283,127 @@ func (c *lsContext) Error() string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var truncMajorPlatforms = []string{
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64",
|
||||||
|
"linux/arm",
|
||||||
|
"linux/ppc64le",
|
||||||
|
"linux/s390x",
|
||||||
|
"linux/riscv64",
|
||||||
|
"linux/mips64",
|
||||||
|
}
|
||||||
|
|
||||||
|
type truncatedPlatforms struct {
|
||||||
|
res map[string][]string
|
||||||
|
input []string
|
||||||
|
max int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp truncatedPlatforms) List() map[string][]string {
|
||||||
|
return tp.res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp truncatedPlatforms) String() string {
|
||||||
|
var out []string
|
||||||
|
var count int
|
||||||
|
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, mpf := range truncMajorPlatforms {
|
||||||
|
if tpf, ok := tp.res[mpf]; ok {
|
||||||
|
seen[mpf] = struct{}{}
|
||||||
|
if len(tpf) == 1 {
|
||||||
|
out = append(out, fmt.Sprintf("%s", tpf[0]))
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
hasPreferredPlatform := false
|
||||||
|
for _, pf := range tpf {
|
||||||
|
if strings.HasSuffix(pf, "*") {
|
||||||
|
hasPreferredPlatform = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainpf := mpf
|
||||||
|
if hasPreferredPlatform {
|
||||||
|
mainpf += "*"
|
||||||
|
}
|
||||||
|
out = append(out, fmt.Sprintf("%s (+%d)", mainpf, len(tpf)))
|
||||||
|
count += len(tpf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for mpf, pf := range tp.res {
|
||||||
|
if len(out) >= tp.max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := seen[mpf]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(pf) == 1 {
|
||||||
|
out = append(out, fmt.Sprintf("%s", pf[0]))
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
hasPreferredPlatform := false
|
||||||
|
for _, pf := range pf {
|
||||||
|
if strings.HasSuffix(pf, "*") {
|
||||||
|
hasPreferredPlatform = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainpf := mpf
|
||||||
|
if hasPreferredPlatform {
|
||||||
|
mainpf += "*"
|
||||||
|
}
|
||||||
|
out = append(out, fmt.Sprintf("%s (+%d)", mainpf, len(pf)))
|
||||||
|
count += len(pf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
left := len(tp.input) - count
|
||||||
|
if left > 0 {
|
||||||
|
out = append(out, fmt.Sprintf("(%d more)", left))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(out, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncPlatforms(pfs []string, max int) truncatedPlatforms {
|
||||||
|
res := make(map[string][]string)
|
||||||
|
for _, mpf := range truncMajorPlatforms {
|
||||||
|
for _, pf := range pfs {
|
||||||
|
if len(res) >= max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pp, err := platforms.Parse(strings.TrimSuffix(pf, "*"))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pp.OS+"/"+pp.Architecture == mpf {
|
||||||
|
res[mpf] = append(res[mpf], pf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
left := make(map[string][]string)
|
||||||
|
for _, pf := range pfs {
|
||||||
|
if len(res) >= max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pp, err := platforms.Parse(strings.TrimSuffix(pf, "*"))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ppf := strings.TrimSuffix(pp.OS+"/"+pp.Architecture, "*")
|
||||||
|
if _, ok := res[ppf]; !ok {
|
||||||
|
left[ppf] = append(left[ppf], pf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range left {
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return truncatedPlatforms{
|
||||||
|
res: res,
|
||||||
|
input: pfs,
|
||||||
|
max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTruncPlatforms(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
platforms []string
|
||||||
|
max int
|
||||||
|
expectedList map[string][]string
|
||||||
|
expectedOut string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "arm64 preferred and emulated",
|
||||||
|
platforms: []string{"linux/arm64*", "linux/amd64", "linux/amd64/v2", "linux/riscv64", "linux/ppc64le", "linux/s390x", "linux/386", "linux/mips64le", "linux/mips64", "linux/arm/v7", "linux/arm/v6"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/amd64": {
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/amd64/v2",
|
||||||
|
},
|
||||||
|
"linux/arm": {
|
||||||
|
"linux/arm/v7",
|
||||||
|
"linux/arm/v6",
|
||||||
|
},
|
||||||
|
"linux/arm64": {
|
||||||
|
"linux/arm64*",
|
||||||
|
},
|
||||||
|
"linux/ppc64le": {
|
||||||
|
"linux/ppc64le",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/amd64 (+2), linux/arm64*, linux/arm (+2), linux/ppc64le, (5 more)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "riscv64 preferred only",
|
||||||
|
platforms: []string{"linux/riscv64*"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/riscv64": {
|
||||||
|
"linux/riscv64*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/riscv64*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amd64 no preferred and emulated",
|
||||||
|
platforms: []string{"linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/386", "linux/arm64", "linux/riscv64", "linux/ppc64le", "linux/s390x", "linux/mips64le", "linux/mips64", "linux/arm/v7", "linux/arm/v6"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/amd64": {
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/amd64/v2",
|
||||||
|
"linux/amd64/v3",
|
||||||
|
},
|
||||||
|
"linux/arm": {
|
||||||
|
"linux/arm/v7",
|
||||||
|
"linux/arm/v6",
|
||||||
|
},
|
||||||
|
"linux/arm64": {
|
||||||
|
"linux/arm64",
|
||||||
|
},
|
||||||
|
"linux/ppc64le": {
|
||||||
|
"linux/ppc64le",
|
||||||
|
}},
|
||||||
|
expectedOut: "linux/amd64 (+3), linux/arm64, linux/arm (+2), linux/ppc64le, (5 more)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "amd64 no preferred",
|
||||||
|
platforms: []string{"linux/amd64", "linux/386"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/386": {
|
||||||
|
"linux/386",
|
||||||
|
},
|
||||||
|
"linux/amd64": {
|
||||||
|
"linux/amd64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/amd64, linux/386",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arm64 no preferred",
|
||||||
|
platforms: []string{"linux/arm64", "linux/arm/v7", "linux/arm/v6"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/arm": {
|
||||||
|
"linux/arm/v7",
|
||||||
|
"linux/arm/v6",
|
||||||
|
},
|
||||||
|
"linux/arm64": {
|
||||||
|
"linux/arm64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/arm64, linux/arm (+2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all preferred",
|
||||||
|
platforms: []string{"darwin/arm64*", "linux/arm64*", "linux/arm/v5*", "linux/arm/v6*", "linux/arm/v7*", "windows/arm64*"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"darwin/arm64": {
|
||||||
|
"darwin/arm64*",
|
||||||
|
},
|
||||||
|
"linux/arm": {
|
||||||
|
"linux/arm/v5*",
|
||||||
|
"linux/arm/v6*",
|
||||||
|
"linux/arm/v7*",
|
||||||
|
},
|
||||||
|
"linux/arm64": {
|
||||||
|
"linux/arm64*",
|
||||||
|
},
|
||||||
|
"windows/arm64": {
|
||||||
|
"windows/arm64*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/arm64*, linux/arm* (+3), darwin/arm64*, windows/arm64*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no major preferred",
|
||||||
|
platforms: []string{"linux/amd64/v2*", "linux/arm/v6*", "linux/mips64le*", "linux/amd64", "linux/amd64/v3", "linux/386", "linux/arm64", "linux/riscv64", "linux/ppc64le", "linux/s390x", "linux/mips64", "linux/arm/v7"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/amd64": {
|
||||||
|
"linux/amd64/v2*",
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/amd64/v3",
|
||||||
|
},
|
||||||
|
"linux/arm": {
|
||||||
|
"linux/arm/v6*",
|
||||||
|
"linux/arm/v7",
|
||||||
|
},
|
||||||
|
"linux/arm64": {
|
||||||
|
"linux/arm64",
|
||||||
|
},
|
||||||
|
"linux/ppc64le": {
|
||||||
|
"linux/ppc64le",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/amd64* (+3), linux/arm64, linux/arm* (+2), linux/ppc64le, (5 more)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no major with multiple variants",
|
||||||
|
platforms: []string{"linux/arm64", "linux/arm/v7", "linux/arm/v6", "linux/mips64le/softfloat", "linux/mips64le/hardfloat"},
|
||||||
|
max: 4,
|
||||||
|
expectedList: map[string][]string{
|
||||||
|
"linux/arm": {
|
||||||
|
"linux/arm/v7",
|
||||||
|
"linux/arm/v6",
|
||||||
|
},
|
||||||
|
"linux/arm64": {
|
||||||
|
"linux/arm64",
|
||||||
|
},
|
||||||
|
"linux/mips64le": {
|
||||||
|
"linux/mips64le/softfloat",
|
||||||
|
"linux/mips64le/hardfloat",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOut: "linux/arm64, linux/arm (+2), linux/mips64le (+2)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tpfs := truncPlatforms(tt.platforms, tt.max)
|
||||||
|
assert.Equal(t, tt.expectedList, tpfs.List())
|
||||||
|
assert.Equal(t, tt.expectedOut, tpfs.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,10 +9,11 @@ List builder instances
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------------|:---------|:--------|:---------------------|
|
|:----------------------|:---------|:--------|:----------------------|
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| [`--format`](#format) | `string` | `table` | Format the output |
|
| [`--format`](#format) | `string` | `table` | Format the output |
|
||||||
|
| `--no-trunc` | `bool` | | Don't truncate output |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
Loading…
Reference in New Issue