зеркало из https://github.com/microsoft/docker.git
Merge pull request #33198 from vdemeester/remove-unused-opts
Remove unused opts (moved to docker/cli)
This commit is contained in:
Коммит
04eb1f0cac
173
opts/mount.go
173
opts/mount.go
|
@ -1,173 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// MountOpt is a Value type for parsing mounts
|
||||
type MountOpt struct {
|
||||
values []mounttypes.Mount
|
||||
}
|
||||
|
||||
// Set a new mount value
|
||||
func (m *MountOpt) Set(value string) error {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mount := mounttypes.Mount{}
|
||||
|
||||
volumeOptions := func() *mounttypes.VolumeOptions {
|
||||
if mount.VolumeOptions == nil {
|
||||
mount.VolumeOptions = &mounttypes.VolumeOptions{
|
||||
Labels: make(map[string]string),
|
||||
}
|
||||
}
|
||||
if mount.VolumeOptions.DriverConfig == nil {
|
||||
mount.VolumeOptions.DriverConfig = &mounttypes.Driver{}
|
||||
}
|
||||
return mount.VolumeOptions
|
||||
}
|
||||
|
||||
bindOptions := func() *mounttypes.BindOptions {
|
||||
if mount.BindOptions == nil {
|
||||
mount.BindOptions = new(mounttypes.BindOptions)
|
||||
}
|
||||
return mount.BindOptions
|
||||
}
|
||||
|
||||
tmpfsOptions := func() *mounttypes.TmpfsOptions {
|
||||
if mount.TmpfsOptions == nil {
|
||||
mount.TmpfsOptions = new(mounttypes.TmpfsOptions)
|
||||
}
|
||||
return mount.TmpfsOptions
|
||||
}
|
||||
|
||||
setValueOnMap := func(target map[string]string, value string) {
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
if len(parts) == 1 {
|
||||
target[value] = ""
|
||||
} else {
|
||||
target[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
mount.Type = mounttypes.TypeVolume // default to volume mounts
|
||||
// Set writable as the default
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
key := strings.ToLower(parts[0])
|
||||
|
||||
if len(parts) == 1 {
|
||||
switch key {
|
||||
case "readonly", "ro":
|
||||
mount.ReadOnly = true
|
||||
continue
|
||||
case "volume-nocopy":
|
||||
volumeOptions().NoCopy = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
}
|
||||
|
||||
value := parts[1]
|
||||
switch key {
|
||||
case "type":
|
||||
mount.Type = mounttypes.Type(strings.ToLower(value))
|
||||
case "source", "src":
|
||||
mount.Source = value
|
||||
case "target", "dst", "destination":
|
||||
mount.Target = value
|
||||
case "readonly", "ro":
|
||||
mount.ReadOnly, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
||||
}
|
||||
case "consistency":
|
||||
mount.Consistency = mounttypes.Consistency(strings.ToLower(value))
|
||||
case "bind-propagation":
|
||||
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
|
||||
case "volume-nocopy":
|
||||
volumeOptions().NoCopy, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for volume-nocopy: %s", value)
|
||||
}
|
||||
case "volume-label":
|
||||
setValueOnMap(volumeOptions().Labels, value)
|
||||
case "volume-driver":
|
||||
volumeOptions().DriverConfig.Name = value
|
||||
case "volume-opt":
|
||||
if volumeOptions().DriverConfig.Options == nil {
|
||||
volumeOptions().DriverConfig.Options = make(map[string]string)
|
||||
}
|
||||
setValueOnMap(volumeOptions().DriverConfig.Options, value)
|
||||
case "tmpfs-size":
|
||||
sizeBytes, err := units.RAMInBytes(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
||||
}
|
||||
tmpfsOptions().SizeBytes = sizeBytes
|
||||
case "tmpfs-mode":
|
||||
ui64, err := strconv.ParseUint(value, 8, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
||||
}
|
||||
tmpfsOptions().Mode = os.FileMode(ui64)
|
||||
default:
|
||||
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
|
||||
}
|
||||
}
|
||||
|
||||
if mount.Type == "" {
|
||||
return fmt.Errorf("type is required")
|
||||
}
|
||||
|
||||
if mount.Target == "" {
|
||||
return fmt.Errorf("target is required")
|
||||
}
|
||||
|
||||
if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
|
||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
|
||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
|
||||
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
|
||||
m.values = append(m.values, mount)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of this option
|
||||
func (m *MountOpt) Type() string {
|
||||
return "mount"
|
||||
}
|
||||
|
||||
// String returns a string repr of this option
|
||||
func (m *MountOpt) String() string {
|
||||
mounts := []string{}
|
||||
for _, mount := range m.values {
|
||||
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
|
||||
mounts = append(mounts, repr)
|
||||
}
|
||||
return strings.Join(mounts, ", ")
|
||||
}
|
||||
|
||||
// Value returns the mounts
|
||||
func (m *MountOpt) Value() []mounttypes.Mount {
|
||||
return m.values
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMountOptString(t *testing.T) {
|
||||
mount := MountOpt{
|
||||
values: []mounttypes.Mount{
|
||||
{
|
||||
Type: mounttypes.TypeBind,
|
||||
Source: "/home/path",
|
||||
Target: "/target",
|
||||
},
|
||||
{
|
||||
Type: mounttypes.TypeVolume,
|
||||
Source: "foo",
|
||||
Target: "/target/foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
expected := "bind /home/path /target, volume foo /target/foo"
|
||||
assert.Equal(t, expected, mount.String())
|
||||
}
|
||||
|
||||
func TestMountOptSetBindNoErrorBind(t *testing.T) {
|
||||
for _, testcase := range []string{
|
||||
// tests several aliases that should have same result.
|
||||
"type=bind,target=/target,source=/source",
|
||||
"type=bind,src=/source,dst=/target",
|
||||
"type=bind,source=/source,dst=/target",
|
||||
"type=bind,src=/source,target=/target",
|
||||
} {
|
||||
var mount MountOpt
|
||||
|
||||
assert.NoError(t, mount.Set(testcase))
|
||||
|
||||
mounts := mount.Value()
|
||||
require.Len(t, mounts, 1)
|
||||
assert.Equal(t, mounttypes.Mount{
|
||||
Type: mounttypes.TypeBind,
|
||||
Source: "/source",
|
||||
Target: "/target",
|
||||
}, mounts[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptSetVolumeNoError(t *testing.T) {
|
||||
for _, testcase := range []string{
|
||||
// tests several aliases that should have same result.
|
||||
"type=volume,target=/target,source=/source",
|
||||
"type=volume,src=/source,dst=/target",
|
||||
"type=volume,source=/source,dst=/target",
|
||||
"type=volume,src=/source,target=/target",
|
||||
} {
|
||||
var mount MountOpt
|
||||
|
||||
assert.NoError(t, mount.Set(testcase))
|
||||
|
||||
mounts := mount.Value()
|
||||
require.Len(t, mounts, 1)
|
||||
assert.Equal(t, mounttypes.Mount{
|
||||
Type: mounttypes.TypeVolume,
|
||||
Source: "/source",
|
||||
Target: "/target",
|
||||
}, mounts[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestMountOptDefaultType ensures that a mount without the type defaults to a
|
||||
// volume mount.
|
||||
func TestMountOptDefaultType(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.NoError(t, mount.Set("target=/target,source=/foo"))
|
||||
assert.Equal(t, mounttypes.TypeVolume, mount.values[0].Type)
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorNoTarget(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.EqualError(t, mount.Set("type=volume,source=/foo"), "target is required")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidKey(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.EqualError(t, mount.Set("type=volume,bogus=foo"), "unexpected key 'bogus' in 'bogus=foo'")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidField(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.EqualError(t, mount.Set("type=volume,bogus"), "invalid field 'bogus' must be a key=value pair")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidReadOnly(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.EqualError(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no")
|
||||
assert.EqualError(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid")
|
||||
}
|
||||
|
||||
func TestMountOptDefaultEnableReadOnly(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo"))
|
||||
assert.False(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly"))
|
||||
assert.True(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1"))
|
||||
assert.True(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=true"))
|
||||
assert.True(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0"))
|
||||
assert.False(t, m.values[0].ReadOnly)
|
||||
}
|
||||
|
||||
func TestMountOptVolumeNoCopy(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.NoError(t, m.Set("type=volume,target=/foo,volume-nocopy"))
|
||||
assert.Equal(t, "", m.values[0].Source)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo"))
|
||||
assert.True(t, m.values[0].VolumeOptions == nil)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true"))
|
||||
assert.True(t, m.values[0].VolumeOptions != nil)
|
||||
assert.True(t, m.values[0].VolumeOptions.NoCopy)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy"))
|
||||
assert.True(t, m.values[0].VolumeOptions != nil)
|
||||
assert.True(t, m.values[0].VolumeOptions.NoCopy)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1"))
|
||||
assert.True(t, m.values[0].VolumeOptions != nil)
|
||||
assert.True(t, m.values[0].VolumeOptions.NoCopy)
|
||||
}
|
||||
|
||||
func TestMountOptTypeConflict(t *testing.T) {
|
||||
var m MountOpt
|
||||
testutil.ErrorContains(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix")
|
||||
testutil.ErrorContains(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix")
|
||||
}
|
||||
|
||||
func TestMountOptSetTmpfsNoError(t *testing.T) {
|
||||
for _, testcase := range []string{
|
||||
// tests several aliases that should have same result.
|
||||
"type=tmpfs,target=/target,tmpfs-size=1m,tmpfs-mode=0700",
|
||||
"type=tmpfs,target=/target,tmpfs-size=1MB,tmpfs-mode=700",
|
||||
} {
|
||||
var mount MountOpt
|
||||
|
||||
assert.NoError(t, mount.Set(testcase))
|
||||
|
||||
mounts := mount.Value()
|
||||
require.Len(t, mounts, 1)
|
||||
assert.Equal(t, mounttypes.Mount{
|
||||
Type: mounttypes.TypeTmpfs,
|
||||
Target: "/target",
|
||||
TmpfsOptions: &mounttypes.TmpfsOptions{
|
||||
SizeBytes: 1024 * 1024, // not 1000 * 1000
|
||||
Mode: os.FileMode(0700),
|
||||
},
|
||||
}, mounts[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptSetTmpfsError(t *testing.T) {
|
||||
var m MountOpt
|
||||
testutil.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-size=foo"), "invalid value for tmpfs-size")
|
||||
testutil.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode")
|
||||
testutil.ErrorContains(t, m.Set("type=tmpfs"), "target is required")
|
||||
}
|
162
opts/port.go
162
opts/port.go
|
@ -1,162 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
const (
|
||||
portOptTargetPort = "target"
|
||||
portOptPublishedPort = "published"
|
||||
portOptProtocol = "protocol"
|
||||
portOptMode = "mode"
|
||||
)
|
||||
|
||||
// PortOpt represents a port config in swarm mode.
|
||||
type PortOpt struct {
|
||||
ports []swarm.PortConfig
|
||||
}
|
||||
|
||||
// Set a new port value
|
||||
func (p *PortOpt) Set(value string) error {
|
||||
longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if longSyntax {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pConfig := swarm.PortConfig{}
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid field %s", field)
|
||||
}
|
||||
|
||||
key := strings.ToLower(parts[0])
|
||||
value := strings.ToLower(parts[1])
|
||||
|
||||
switch key {
|
||||
case portOptProtocol:
|
||||
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
|
||||
return fmt.Errorf("invalid protocol value %s", value)
|
||||
}
|
||||
|
||||
pConfig.Protocol = swarm.PortConfigProtocol(value)
|
||||
case portOptMode:
|
||||
if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
|
||||
return fmt.Errorf("invalid publish mode value %s", value)
|
||||
}
|
||||
|
||||
pConfig.PublishMode = swarm.PortConfigPublishMode(value)
|
||||
case portOptTargetPort:
|
||||
tPort, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pConfig.TargetPort = uint32(tPort)
|
||||
case portOptPublishedPort:
|
||||
pPort, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pConfig.PublishedPort = uint32(pPort)
|
||||
default:
|
||||
return fmt.Errorf("invalid field key %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
if pConfig.TargetPort == 0 {
|
||||
return fmt.Errorf("missing mandatory field %q", portOptTargetPort)
|
||||
}
|
||||
|
||||
if pConfig.PublishMode == "" {
|
||||
pConfig.PublishMode = swarm.PortConfigPublishModeIngress
|
||||
}
|
||||
|
||||
if pConfig.Protocol == "" {
|
||||
pConfig.Protocol = swarm.PortConfigProtocolTCP
|
||||
}
|
||||
|
||||
p.ports = append(p.ports, pConfig)
|
||||
} else {
|
||||
// short syntax
|
||||
portConfigs := []swarm.PortConfig{}
|
||||
ports, portBindingMap, err := nat.ParsePortSpecs([]string{value})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, portBindings := range portBindingMap {
|
||||
for _, portBinding := range portBindings {
|
||||
if portBinding.HostIP != "" {
|
||||
return fmt.Errorf("HostIP is not supported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for port := range ports {
|
||||
portConfig, err := ConvertPortToPortConfig(port, portBindingMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
portConfigs = append(portConfigs, portConfig...)
|
||||
}
|
||||
p.ports = append(p.ports, portConfigs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of this option
|
||||
func (p *PortOpt) Type() string {
|
||||
return "port"
|
||||
}
|
||||
|
||||
// String returns a string repr of this option
|
||||
func (p *PortOpt) String() string {
|
||||
ports := []string{}
|
||||
for _, port := range p.ports {
|
||||
repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
|
||||
ports = append(ports, repr)
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
}
|
||||
|
||||
// Value returns the ports
|
||||
func (p *PortOpt) Value() []swarm.PortConfig {
|
||||
return p.ports
|
||||
}
|
||||
|
||||
// ConvertPortToPortConfig converts ports to the swarm type
|
||||
func ConvertPortToPortConfig(
|
||||
port nat.Port,
|
||||
portBindings map[nat.Port][]nat.PortBinding,
|
||||
) ([]swarm.PortConfig, error) {
|
||||
ports := []swarm.PortConfig{}
|
||||
|
||||
for _, binding := range portBindings[port] {
|
||||
hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
||||
if err != nil && binding.HostPort != "" {
|
||||
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
|
||||
}
|
||||
ports = append(ports, swarm.PortConfig{
|
||||
//TODO Name: ?
|
||||
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
|
||||
TargetPort: uint32(port.Int()),
|
||||
PublishedPort: uint32(hostPort),
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
|
@ -1,296 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPortOptValidSimpleSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected []swarm.PortConfig
|
||||
}{
|
||||
{
|
||||
value: "80",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80:8080",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "8080:80/tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80:8080/udp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80-81:8080-8081/tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8081,
|
||||
PublishedPort: 81,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80-82:8080-8082/udp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 8081,
|
||||
PublishedPort: 81,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 8082,
|
||||
PublishedPort: 82,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.NoError(t, port.Set(tc.value))
|
||||
assert.Len(t, port.Value(), len(tc.expected))
|
||||
for _, expectedPortConfig := range tc.expected {
|
||||
assertContains(t, port.Value(), expectedPortConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortOptValidComplexSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected []swarm.PortConfig
|
||||
}{
|
||||
{
|
||||
value: "target=80",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
TargetPort: 80,
|
||||
Protocol: "tcp",
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,protocol=tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,published=8080,protocol=tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "published=80,target=8080,protocol=tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,published=8080,protocol=tcp,mode=host",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: "host",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,published=8080,mode=host",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: "host",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,published=8080,mode=ingress",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: "ingress",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.NoError(t, port.Set(tc.value))
|
||||
assert.Len(t, port.Value(), len(tc.expected))
|
||||
for _, expectedPortConfig := range tc.expected {
|
||||
assertContains(t, port.Value(), expectedPortConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortOptInvalidComplexSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
value: "invalid,target=80",
|
||||
expectedError: "invalid field",
|
||||
},
|
||||
{
|
||||
value: "invalid=field",
|
||||
expectedError: "invalid field",
|
||||
},
|
||||
{
|
||||
value: "protocol=invalid",
|
||||
expectedError: "invalid protocol value",
|
||||
},
|
||||
{
|
||||
value: "target=invalid",
|
||||
expectedError: "invalid syntax",
|
||||
},
|
||||
{
|
||||
value: "published=invalid",
|
||||
expectedError: "invalid syntax",
|
||||
},
|
||||
{
|
||||
value: "mode=invalid",
|
||||
expectedError: "invalid publish mode value",
|
||||
},
|
||||
{
|
||||
value: "published=8080,protocol=tcp,mode=ingress",
|
||||
expectedError: "missing mandatory field",
|
||||
},
|
||||
{
|
||||
value: `target=80,protocol="tcp,mode=ingress"`,
|
||||
expectedError: "non-quoted-field",
|
||||
},
|
||||
{
|
||||
value: `target=80,"protocol=tcp,mode=ingress"`,
|
||||
expectedError: "invalid protocol value",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
testutil.ErrorContains(t, port.Set(tc.value), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortOptInvalidSimpleSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
value: "9999999",
|
||||
expectedError: "Invalid containerPort: 9999999",
|
||||
},
|
||||
{
|
||||
value: "80/xyz",
|
||||
expectedError: "Invalid proto: xyz",
|
||||
},
|
||||
{
|
||||
value: "tcp",
|
||||
expectedError: "Invalid containerPort: tcp",
|
||||
},
|
||||
{
|
||||
value: "udp",
|
||||
expectedError: "Invalid containerPort: udp",
|
||||
},
|
||||
{
|
||||
value: "",
|
||||
expectedError: "No port specified: <empty>",
|
||||
},
|
||||
{
|
||||
value: "1.1.1.1:80:80",
|
||||
expectedError: "HostIP is not supported.",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.EqualError(t, port.Set(tc.value), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func assertContains(t *testing.T, portConfigs []swarm.PortConfig, expected swarm.PortConfig) {
|
||||
var contains = false
|
||||
for _, portConfig := range portConfigs {
|
||||
if portConfig == expected {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !contains {
|
||||
t.Errorf("expected %v to contain %v, did not", portConfigs, expected)
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
// SecretOpt is a Value type for parsing secrets
|
||||
type SecretOpt struct {
|
||||
values []*swarmtypes.SecretReference
|
||||
}
|
||||
|
||||
// Set a new secret value
|
||||
func (o *SecretOpt) Set(value string) error {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := &swarmtypes.SecretReference{
|
||||
File: &swarmtypes.SecretReferenceFileTarget{
|
||||
UID: "0",
|
||||
GID: "0",
|
||||
Mode: 0444,
|
||||
},
|
||||
}
|
||||
|
||||
// support a simple syntax of --secret foo
|
||||
if len(fields) == 1 {
|
||||
options.File.Name = fields[0]
|
||||
options.SecretName = fields[0]
|
||||
o.values = append(o.values, options)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
key := strings.ToLower(parts[0])
|
||||
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
}
|
||||
|
||||
value := parts[1]
|
||||
switch key {
|
||||
case "source", "src":
|
||||
options.SecretName = value
|
||||
case "target":
|
||||
options.File.Name = value
|
||||
case "uid":
|
||||
options.File.UID = value
|
||||
case "gid":
|
||||
options.File.GID = value
|
||||
case "mode":
|
||||
m, err := strconv.ParseUint(value, 0, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid mode specified: %v", err)
|
||||
}
|
||||
|
||||
options.File.Mode = os.FileMode(m)
|
||||
default:
|
||||
return fmt.Errorf("invalid field in secret request: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
if options.SecretName == "" {
|
||||
return fmt.Errorf("source is required")
|
||||
}
|
||||
|
||||
o.values = append(o.values, options)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of this option
|
||||
func (o *SecretOpt) Type() string {
|
||||
return "secret"
|
||||
}
|
||||
|
||||
// String returns a string repr of this option
|
||||
func (o *SecretOpt) String() string {
|
||||
secrets := []string{}
|
||||
for _, secret := range o.values {
|
||||
repr := fmt.Sprintf("%s -> %s", secret.SecretName, secret.File.Name)
|
||||
secrets = append(secrets, repr)
|
||||
}
|
||||
return strings.Join(secrets, ", ")
|
||||
}
|
||||
|
||||
// Value returns the secret requests
|
||||
func (o *SecretOpt) Value() []*swarmtypes.SecretReference {
|
||||
return o.values
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSecretOptionsSimple(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "app-secret"
|
||||
assert.NoError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
require.Len(t, reqs, 1)
|
||||
req := reqs[0]
|
||||
assert.Equal(t, "app-secret", req.SecretName)
|
||||
assert.Equal(t, "app-secret", req.File.Name)
|
||||
assert.Equal(t, "0", req.File.UID)
|
||||
assert.Equal(t, "0", req.File.GID)
|
||||
}
|
||||
|
||||
func TestSecretOptionsSourceTarget(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "source=foo,target=testing"
|
||||
assert.NoError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
require.Len(t, reqs, 1)
|
||||
req := reqs[0]
|
||||
assert.Equal(t, "foo", req.SecretName)
|
||||
assert.Equal(t, "testing", req.File.Name)
|
||||
}
|
||||
|
||||
func TestSecretOptionsShorthand(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "src=foo,target=testing"
|
||||
assert.NoError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
require.Len(t, reqs, 1)
|
||||
req := reqs[0]
|
||||
assert.Equal(t, "foo", req.SecretName)
|
||||
}
|
||||
|
||||
func TestSecretOptionsCustomUidGid(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "source=foo,target=testing,uid=1000,gid=1001"
|
||||
assert.NoError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
require.Len(t, reqs, 1)
|
||||
req := reqs[0]
|
||||
assert.Equal(t, "foo", req.SecretName)
|
||||
assert.Equal(t, "testing", req.File.Name)
|
||||
assert.Equal(t, "1000", req.File.UID)
|
||||
assert.Equal(t, "1001", req.File.GID)
|
||||
}
|
||||
|
||||
func TestSecretOptionsCustomMode(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "source=foo,target=testing,uid=1000,gid=1001,mode=0444"
|
||||
assert.NoError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
require.Len(t, reqs, 1)
|
||||
req := reqs[0]
|
||||
assert.Equal(t, "foo", req.SecretName)
|
||||
assert.Equal(t, "testing", req.File.Name)
|
||||
assert.Equal(t, "1000", req.File.UID)
|
||||
assert.Equal(t, "1001", req.File.GID)
|
||||
assert.Equal(t, os.FileMode(0444), req.File.Mode)
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
)
|
||||
|
||||
// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.
|
||||
type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error)
|
||||
|
||||
// ValidateWeightDevice validates that the specified string has a valid device-weight format.
|
||||
func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) {
|
||||
split := strings.SplitN(val, ":", 2)
|
||||
if len(split) != 2 {
|
||||
return nil, fmt.Errorf("bad format: %s", val)
|
||||
}
|
||||
if !strings.HasPrefix(split[0], "/dev/") {
|
||||
return nil, fmt.Errorf("bad format for device path: %s", val)
|
||||
}
|
||||
weight, err := strconv.ParseUint(split[1], 10, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid weight for device: %s", val)
|
||||
}
|
||||
if weight > 0 && (weight < 10 || weight > 1000) {
|
||||
return nil, fmt.Errorf("invalid weight for device: %s", val)
|
||||
}
|
||||
|
||||
return &blkiodev.WeightDevice{
|
||||
Path: split[0],
|
||||
Weight: uint16(weight),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WeightdeviceOpt defines a map of WeightDevices
|
||||
type WeightdeviceOpt struct {
|
||||
values []*blkiodev.WeightDevice
|
||||
validator ValidatorWeightFctType
|
||||
}
|
||||
|
||||
// NewWeightdeviceOpt creates a new WeightdeviceOpt
|
||||
func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt {
|
||||
values := []*blkiodev.WeightDevice{}
|
||||
return WeightdeviceOpt{
|
||||
values: values,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt
|
||||
func (opt *WeightdeviceOpt) Set(val string) error {
|
||||
var value *blkiodev.WeightDevice
|
||||
if opt.validator != nil {
|
||||
v, err := opt.validator(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value = v
|
||||
}
|
||||
(opt.values) = append((opt.values), value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns WeightdeviceOpt values as a string.
|
||||
func (opt *WeightdeviceOpt) String() string {
|
||||
var out []string
|
||||
for _, v := range opt.values {
|
||||
out = append(out, v.String())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", out)
|
||||
}
|
||||
|
||||
// GetList returns a slice of pointers to WeightDevices.
|
||||
func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
|
||||
var weightdevice []*blkiodev.WeightDevice
|
||||
for _, v := range opt.values {
|
||||
weightdevice = append(weightdevice, v)
|
||||
}
|
||||
|
||||
return weightdevice
|
||||
}
|
||||
|
||||
// Type returns the option type
|
||||
func (opt *WeightdeviceOpt) Type() string {
|
||||
return "list"
|
||||
}
|
Загрузка…
Ссылка в новой задаче