Skip to content

Commit 774f1d6

Browse files
committed
cli/command/service: parse generic resources without protobufs
This code was using swarmkit's genericresource package as intermediate; add a local copy of that code that skips the protobufs as intermediate. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 9fc247e commit 774f1d6

File tree

10 files changed

+157
-491
lines changed

10 files changed

+157
-491
lines changed

cli/command/service/generic_resource_opts.go

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import (
44
"fmt"
55
"strings"
66

7+
"github.com/docker/cli/cli/command/service/internal/genericresource"
78
"github.com/moby/moby/api/types/swarm"
8-
swarmapi "github.com/moby/swarmkit/v2/api"
9-
"github.com/moby/swarmkit/v2/api/genericresource"
109
)
1110

1211
// GenericResource is a concept that a user can use to advertise user-defined
@@ -33,12 +32,11 @@ func ParseGenericResources(value []string) ([]swarm.GenericResource, error) {
3332
return nil, nil
3433
}
3534

36-
resources, err := genericresource.Parse(value)
35+
swarmResources, err := genericresource.Parse(value)
3736
if err != nil {
3837
return nil, fmt.Errorf("invalid generic resource specification: %w", err)
3938
}
4039

41-
swarmResources := genericResourcesFromGRPC(resources)
4240
for _, res := range swarmResources {
4341
if res.NamedResourceSpec != nil {
4442
return nil, fmt.Errorf("invalid generic-resource request `%s=%s`, Named Generic Resources is not supported for service create or update",
@@ -50,31 +48,6 @@ func ParseGenericResources(value []string) ([]swarm.GenericResource, error) {
5048
return swarmResources, nil
5149
}
5250

53-
// genericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource
54-
func genericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []swarm.GenericResource {
55-
generic := make([]swarm.GenericResource, 0, len(genericRes))
56-
for _, res := range genericRes {
57-
var current swarm.GenericResource
58-
59-
switch r := res.Resource.(type) {
60-
case *swarmapi.GenericResource_DiscreteResourceSpec:
61-
current.DiscreteResourceSpec = &swarm.DiscreteGenericResource{
62-
Kind: r.DiscreteResourceSpec.Kind,
63-
Value: r.DiscreteResourceSpec.Value,
64-
}
65-
case *swarmapi.GenericResource_NamedResourceSpec:
66-
current.NamedResourceSpec = &swarm.NamedGenericResource{
67-
Kind: r.NamedResourceSpec.Kind,
68-
Value: r.NamedResourceSpec.Value,
69-
}
70-
}
71-
72-
generic = append(generic, current)
73-
}
74-
75-
return generic
76-
}
77-
7851
func buildGenericResourceMap(genericRes []swarm.GenericResource) (map[string]swarm.GenericResource, error) {
7952
m := make(map[string]swarm.GenericResource)
8053

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package genericresource
2+
3+
import (
4+
api "github.com/moby/moby/api/types/swarm"
5+
)
6+
7+
// NewSet creates a set object
8+
func NewSet(key string, vals ...string) []api.GenericResource {
9+
rs := make([]api.GenericResource, 0, len(vals))
10+
for _, v := range vals {
11+
rs = append(rs, NewString(key, v))
12+
}
13+
return rs
14+
}
15+
16+
// NewString creates a String resource
17+
func NewString(kind, value string) api.GenericResource {
18+
return api.GenericResource{
19+
NamedResourceSpec: &api.NamedGenericResource{
20+
Kind: kind,
21+
Value: value,
22+
},
23+
}
24+
}
25+
26+
// NewDiscrete creates a Discrete resource
27+
func NewDiscrete(key string, val int64) api.GenericResource {
28+
return api.GenericResource{
29+
DiscreteResourceSpec: &api.DiscreteGenericResource{
30+
Kind: key,
31+
Value: val,
32+
},
33+
}
34+
}
35+
36+
// GetResource returns resources from the "resources" parameter matching the kind key
37+
func GetResource(kind string, resources []api.GenericResource) []api.GenericResource {
38+
var res []api.GenericResource
39+
for _, r := range resources {
40+
switch {
41+
case r.DiscreteResourceSpec != nil && r.DiscreteResourceSpec.Kind == kind:
42+
res = append(res, r)
43+
case r.NamedResourceSpec != nil && r.NamedResourceSpec.Kind == kind:
44+
res = append(res, r)
45+
}
46+
}
47+
return res
48+
}

vendor/github.com/moby/swarmkit/v2/api/genericresource/parse.go renamed to cli/command/service/internal/genericresource/parse.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
2+
//go:build go1.24
3+
4+
// Package genericresource is a local fork of SwarmKit's [genericresource] package,
5+
// without protobuf dependencies.
6+
//
7+
// [genericresource]: https://github.com/moby/swarmkit/blob/v2.1.1/api/genericresource/parse.go
18
package genericresource
29

310
import (
@@ -6,10 +13,10 @@ import (
613
"strconv"
714
"strings"
815

9-
"github.com/moby/swarmkit/v2/api"
16+
api "github.com/moby/moby/api/types/swarm"
1017
)
1118

12-
func newParseError(format string, args ...interface{}) error {
19+
func newParseError(format string, args ...any) error {
1320
return fmt.Errorf("could not parse GenericResource: "+format, args...)
1421
}
1522

@@ -32,15 +39,14 @@ func allNamedResources(res []string) bool {
3239
}
3340

3441
// ParseCmd parses the Generic Resource command line argument
35-
// and returns a list of *api.GenericResource
36-
func ParseCmd(cmd string) ([]*api.GenericResource, error) {
42+
// and returns a list of api.GenericResource
43+
func ParseCmd(cmd string) ([]api.GenericResource, error) {
3744
if strings.Contains(cmd, "\n") {
3845
return nil, newParseError("unexpected '\\n' character")
3946
}
4047

4148
r := csv.NewReader(strings.NewReader(cmd))
4249
records, err := r.ReadAll()
43-
4450
if err != nil {
4551
return nil, newParseError("%v", err)
4652
}
@@ -53,7 +59,7 @@ func ParseCmd(cmd string) ([]*api.GenericResource, error) {
5359
}
5460

5561
// Parse parses a table of GenericResource resources
56-
func Parse(cmds []string) ([]*api.GenericResource, error) {
62+
func Parse(cmds []string) ([]api.GenericResource, error) {
5763
tokens := make(map[string][]string)
5864

5965
for _, term := range cmds {
@@ -69,7 +75,7 @@ func Parse(cmds []string) ([]*api.GenericResource, error) {
6975
tokens[key] = append(tokens[key], val)
7076
}
7177

72-
var rs []*api.GenericResource
78+
var rs []api.GenericResource
7379
for k, v := range tokens {
7480
if u, ok := isDiscreteResource(v); ok {
7581
if u < 0 {
@@ -107,5 +113,4 @@ func isDiscreteResource(values []string) (int64, bool) {
107113
}
108114

109115
return u, true
110-
111116
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package genericresource
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/v3/assert"
7+
)
8+
9+
func TestParseDiscrete(t *testing.T) {
10+
res, err := ParseCmd("apple=3")
11+
assert.NilError(t, err)
12+
assert.Equal(t, len(res), 1)
13+
14+
apples := GetResource("apple", res)
15+
assert.Equal(t, len(apples), 1)
16+
if apples[0].DiscreteResourceSpec == nil {
17+
t.Fatalf("expected discrete resource spec, got nil")
18+
}
19+
assert.Equal(t, apples[0].DiscreteResourceSpec.Value, int64(3))
20+
21+
_, err = ParseCmd("apple=3\napple=4")
22+
assert.Assert(t, err != nil)
23+
24+
_, err = ParseCmd("apple=3,apple=4")
25+
assert.Assert(t, err != nil)
26+
27+
_, err = ParseCmd("apple=-3")
28+
assert.Assert(t, err != nil)
29+
}
30+
31+
func TestParseStr(t *testing.T) {
32+
res, err := ParseCmd("orange=red,orange=green,orange=blue")
33+
assert.NilError(t, err)
34+
assert.Equal(t, len(res), 3)
35+
36+
oranges := GetResource("orange", res)
37+
assert.Equal(t, len(oranges), 3)
38+
for _, k := range []string{"red", "green", "blue"} {
39+
assert.Assert(t, HasResource(NewString("orange", k), oranges))
40+
}
41+
}
42+
43+
func TestParseDiscreteAndStr(t *testing.T) {
44+
res, err := ParseCmd("orange=red,orange=green,orange=blue,apple=3")
45+
assert.NilError(t, err)
46+
assert.Equal(t, len(res), 4)
47+
48+
oranges := GetResource("orange", res)
49+
assert.Equal(t, len(oranges), 3)
50+
for _, k := range []string{"red", "green", "blue"} {
51+
assert.Assert(t, HasResource(NewString("orange", k), oranges))
52+
}
53+
54+
apples := GetResource("apple", res)
55+
assert.Equal(t, len(apples), 1)
56+
if apples[0].DiscreteResourceSpec == nil {
57+
t.Fatalf("expected discrete resource spec, got nil")
58+
}
59+
assert.Equal(t, apples[0].DiscreteResourceSpec.Value, int64(3))
60+
}
61+
62+
func TestParseMixedForSameKindFails(t *testing.T) {
63+
_, err := ParseCmd("gpu=fast,gpu=slow,gpu=2")
64+
assert.Assert(t, err != nil)
65+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package genericresource
2+
3+
import (
4+
api "github.com/moby/moby/api/types/swarm"
5+
)
6+
7+
// HasResource checks if there is enough "res" in the "resources" argument
8+
func HasResource(res api.GenericResource, resources []api.GenericResource) bool {
9+
for _, r := range resources {
10+
if equalResource(r, res) {
11+
return true
12+
}
13+
}
14+
return false
15+
}
16+
17+
// equalResource matches the resource *type* (named vs discrete), and then kind+value.
18+
func equalResource(a, b api.GenericResource) bool {
19+
switch {
20+
case a.NamedResourceSpec != nil && b.NamedResourceSpec != nil:
21+
return a.NamedResourceSpec.Kind == b.NamedResourceSpec.Kind &&
22+
a.NamedResourceSpec.Value == b.NamedResourceSpec.Value
23+
24+
case a.DiscreteResourceSpec != nil && b.DiscreteResourceSpec != nil:
25+
return a.DiscreteResourceSpec.Kind == b.DiscreteResourceSpec.Kind &&
26+
a.DiscreteResourceSpec.Value == b.DiscreteResourceSpec.Value
27+
}
28+
return false
29+
}

vendor/github.com/moby/swarmkit/v2/api/genericresource/helpers.go

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)