Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions cli/command/service/generic_resource_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import (
"fmt"
"strings"

"github.com/docker/cli/cli/command/service/internal/genericresource"
"github.com/moby/moby/api/types/swarm"
swarmapi "github.com/moby/swarmkit/v2/api"
"github.com/moby/swarmkit/v2/api/genericresource"
)

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

resources, err := genericresource.Parse(value)
swarmResources, err := genericresource.Parse(value)
if err != nil {
return nil, fmt.Errorf("invalid generic resource specification: %w", err)
}

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

// genericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource
func genericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []swarm.GenericResource {
generic := make([]swarm.GenericResource, 0, len(genericRes))
for _, res := range genericRes {
var current swarm.GenericResource

switch r := res.Resource.(type) {
case *swarmapi.GenericResource_DiscreteResourceSpec:
current.DiscreteResourceSpec = &swarm.DiscreteGenericResource{
Kind: r.DiscreteResourceSpec.Kind,
Value: r.DiscreteResourceSpec.Value,
}
case *swarmapi.GenericResource_NamedResourceSpec:
current.NamedResourceSpec = &swarm.NamedGenericResource{
Kind: r.NamedResourceSpec.Kind,
Value: r.NamedResourceSpec.Value,
}
}

generic = append(generic, current)
}

return generic
}

func buildGenericResourceMap(genericRes []swarm.GenericResource) (map[string]swarm.GenericResource, error) {
m := make(map[string]swarm.GenericResource)

Expand Down
48 changes: 48 additions & 0 deletions cli/command/service/internal/genericresource/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package genericresource

import (
api "github.com/moby/moby/api/types/swarm"
)

// NewSet creates a set object
func NewSet(key string, vals ...string) []api.GenericResource {
rs := make([]api.GenericResource, 0, len(vals))
for _, v := range vals {
rs = append(rs, NewString(key, v))
}
return rs
}

// NewString creates a String resource
func NewString(kind, value string) api.GenericResource {
return api.GenericResource{
NamedResourceSpec: &api.NamedGenericResource{
Kind: kind,
Value: value,
},
}
}

// NewDiscrete creates a Discrete resource
func NewDiscrete(key string, val int64) api.GenericResource {
return api.GenericResource{
DiscreteResourceSpec: &api.DiscreteGenericResource{
Kind: key,
Value: val,
},
}
}

// GetResource returns resources from the "resources" parameter matching the kind key
func GetResource(kind string, resources []api.GenericResource) []api.GenericResource {
var res []api.GenericResource
for _, r := range resources {
switch {
case r.DiscreteResourceSpec != nil && r.DiscreteResourceSpec.Kind == kind:
res = append(res, r)
case r.NamedResourceSpec != nil && r.NamedResourceSpec.Kind == kind:
res = append(res, r)
}
}
return res
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24

// Package genericresource is a local fork of SwarmKit's [genericresource] package,
// without protobuf dependencies.
//
// [genericresource]: https://github.com/moby/swarmkit/blob/v2.1.1/api/genericresource/parse.go
package genericresource

import (
Expand All @@ -6,10 +13,10 @@ import (
"strconv"
"strings"

"github.com/moby/swarmkit/v2/api"
api "github.com/moby/moby/api/types/swarm"
)

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

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

// ParseCmd parses the Generic Resource command line argument
// and returns a list of *api.GenericResource
func ParseCmd(cmd string) ([]*api.GenericResource, error) {
// and returns a list of api.GenericResource
func ParseCmd(cmd string) ([]api.GenericResource, error) {
if strings.Contains(cmd, "\n") {
return nil, newParseError("unexpected '\\n' character")
}

r := csv.NewReader(strings.NewReader(cmd))
records, err := r.ReadAll()

if err != nil {
return nil, newParseError("%v", err)
}
Expand All @@ -53,7 +59,7 @@ func ParseCmd(cmd string) ([]*api.GenericResource, error) {
}

// Parse parses a table of GenericResource resources
func Parse(cmds []string) ([]*api.GenericResource, error) {
func Parse(cmds []string) ([]api.GenericResource, error) {
tokens := make(map[string][]string)

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

var rs []*api.GenericResource
var rs []api.GenericResource
for k, v := range tokens {
if u, ok := isDiscreteResource(v); ok {
if u < 0 {
Expand Down Expand Up @@ -107,5 +113,4 @@ func isDiscreteResource(values []string) (int64, bool) {
}

return u, true

}
65 changes: 65 additions & 0 deletions cli/command/service/internal/genericresource/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package genericresource

import (
"testing"

"gotest.tools/v3/assert"
)

func TestParseDiscrete(t *testing.T) {
res, err := ParseCmd("apple=3")
assert.NilError(t, err)
assert.Equal(t, len(res), 1)

apples := GetResource("apple", res)
assert.Equal(t, len(apples), 1)
if apples[0].DiscreteResourceSpec == nil {
t.Fatalf("expected discrete resource spec, got nil")
}
assert.Equal(t, apples[0].DiscreteResourceSpec.Value, int64(3))

_, err = ParseCmd("apple=3\napple=4")
assert.Assert(t, err != nil)

_, err = ParseCmd("apple=3,apple=4")
assert.Assert(t, err != nil)

_, err = ParseCmd("apple=-3")
assert.Assert(t, err != nil)
}

func TestParseStr(t *testing.T) {
res, err := ParseCmd("orange=red,orange=green,orange=blue")
assert.NilError(t, err)
assert.Equal(t, len(res), 3)

oranges := GetResource("orange", res)
assert.Equal(t, len(oranges), 3)
for _, k := range []string{"red", "green", "blue"} {
assert.Assert(t, HasResource(NewString("orange", k), oranges))
}
}

func TestParseDiscreteAndStr(t *testing.T) {
res, err := ParseCmd("orange=red,orange=green,orange=blue,apple=3")
assert.NilError(t, err)
assert.Equal(t, len(res), 4)

oranges := GetResource("orange", res)
assert.Equal(t, len(oranges), 3)
for _, k := range []string{"red", "green", "blue"} {
assert.Assert(t, HasResource(NewString("orange", k), oranges))
}

apples := GetResource("apple", res)
assert.Equal(t, len(apples), 1)
if apples[0].DiscreteResourceSpec == nil {
t.Fatalf("expected discrete resource spec, got nil")
}
assert.Equal(t, apples[0].DiscreteResourceSpec.Value, int64(3))
}

func TestParseMixedForSameKindFails(t *testing.T) {
_, err := ParseCmd("gpu=fast,gpu=slow,gpu=2")
assert.Assert(t, err != nil)
}
29 changes: 29 additions & 0 deletions cli/command/service/internal/genericresource/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package genericresource

import (
api "github.com/moby/moby/api/types/swarm"
)

// HasResource checks if there is enough "res" in the "resources" argument
func HasResource(res api.GenericResource, resources []api.GenericResource) bool {
for _, r := range resources {
if equalResource(r, res) {
return true
}
}
return false
}

// equalResource matches the resource *type* (named vs discrete), and then kind+value.
func equalResource(a, b api.GenericResource) bool {
switch {
case a.NamedResourceSpec != nil && b.NamedResourceSpec != nil:
return a.NamedResourceSpec.Kind == b.NamedResourceSpec.Kind &&
a.NamedResourceSpec.Value == b.NamedResourceSpec.Value

case a.DiscreteResourceSpec != nil && b.DiscreteResourceSpec != nil:
return a.DiscreteResourceSpec.Kind == b.DiscreteResourceSpec.Kind &&
a.DiscreteResourceSpec.Value == b.DiscreteResourceSpec.Value
}
return false
}
111 changes: 0 additions & 111 deletions vendor/github.com/moby/swarmkit/v2/api/genericresource/helpers.go

This file was deleted.

Loading
Loading