Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -50,6 +50,16 @@ const ( |
| 50 | 50 |
PropagationSlave Propagation = "slave" |
| 51 | 51 |
) |
| 52 | 52 |
|
| 53 |
+// Propagations is the list of all valid mount propagations |
|
| 54 |
+var Propagations = []Propagation{
|
|
| 55 |
+ PropagationRPrivate, |
|
| 56 |
+ PropagationPrivate, |
|
| 57 |
+ PropagationRShared, |
|
| 58 |
+ PropagationShared, |
|
| 59 |
+ PropagationRSlave, |
|
| 60 |
+ PropagationSlave, |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 53 | 63 |
// BindOptions defines options specific to mounts of type "bind". |
| 54 | 64 |
type BindOptions struct {
|
| 55 | 65 |
Propagation Propagation `json:",omitempty"` |
| ... | ... |
@@ -7,11 +7,12 @@ import ( |
| 7 | 7 |
"github.com/docker/docker/api/types/filters" |
| 8 | 8 |
"github.com/docker/docker/api/types/swarm" |
| 9 | 9 |
"github.com/docker/docker/client" |
| 10 |
+ "github.com/docker/docker/pkg/composetransform" |
|
| 10 | 11 |
) |
| 11 | 12 |
|
| 12 | 13 |
func getStackFilter(namespace string) filters.Args {
|
| 13 | 14 |
filter := filters.NewArgs() |
| 14 |
- filter.Add("label", labelNamespace+"="+namespace)
|
|
| 15 |
+ filter.Add("label", composetransform.LabelNamespace+"="+namespace)
|
|
| 15 | 16 |
return filter |
| 16 | 17 |
} |
| 17 | 18 |
|
| ... | ... |
@@ -16,13 +16,12 @@ import ( |
| 16 | 16 |
composetypes "github.com/aanand/compose-file/types" |
| 17 | 17 |
"github.com/docker/docker/api/types" |
| 18 | 18 |
"github.com/docker/docker/api/types/container" |
| 19 |
- "github.com/docker/docker/api/types/mount" |
|
| 20 |
- networktypes "github.com/docker/docker/api/types/network" |
|
| 21 | 19 |
"github.com/docker/docker/api/types/swarm" |
| 22 | 20 |
"github.com/docker/docker/cli" |
| 23 | 21 |
"github.com/docker/docker/cli/command" |
| 24 | 22 |
dockerclient "github.com/docker/docker/client" |
| 25 | 23 |
"github.com/docker/docker/opts" |
| 24 |
+ "github.com/docker/docker/pkg/composetransform" |
|
| 26 | 25 |
runconfigopts "github.com/docker/docker/runconfig/opts" |
| 27 | 26 |
"github.com/docker/go-connections/nat" |
| 28 | 27 |
) |
| ... | ... |
@@ -121,9 +120,9 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo |
| 121 | 121 |
return err |
| 122 | 122 |
} |
| 123 | 123 |
|
| 124 |
- namespace := namespace{name: opts.namespace}
|
|
| 124 |
+ namespace := composetransform.NewNamespace(opts.namespace) |
|
| 125 | 125 |
|
| 126 |
- networks, externalNetworks := convertNetworks(namespace, config.Networks) |
|
| 126 |
+ networks, externalNetworks := composetransform.ConvertNetworks(namespace, config.Networks) |
|
| 127 | 127 |
if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
|
| 128 | 128 |
return err |
| 129 | 129 |
} |
| ... | ... |
@@ -204,12 +203,12 @@ func validateExternalNetworks( |
| 204 | 204 |
func createNetworks( |
| 205 | 205 |
ctx context.Context, |
| 206 | 206 |
dockerCli *command.DockerCli, |
| 207 |
- namespace namespace, |
|
| 207 |
+ namespace composetransform.Namespace, |
|
| 208 | 208 |
networks map[string]types.NetworkCreate, |
| 209 | 209 |
) error {
|
| 210 | 210 |
client := dockerCli.Client() |
| 211 | 211 |
|
| 212 |
- existingNetworks, err := getStackNetworks(ctx, client, namespace.name) |
|
| 212 |
+ existingNetworks, err := getStackNetworks(ctx, client, namespace.Name()) |
|
| 213 | 213 |
if err != nil {
|
| 214 | 214 |
return err |
| 215 | 215 |
} |
| ... | ... |
@@ -220,7 +219,7 @@ func createNetworks( |
| 220 | 220 |
} |
| 221 | 221 |
|
| 222 | 222 |
for internalName, createOpts := range networks {
|
| 223 |
- name := namespace.scope(internalName) |
|
| 223 |
+ name := namespace.Scope(internalName) |
|
| 224 | 224 |
if _, exists := existingNetworkMap[name]; exists {
|
| 225 | 225 |
continue |
| 226 | 226 |
} |
| ... | ... |
@@ -241,7 +240,7 @@ func createNetworks( |
| 241 | 241 |
func convertServiceNetworks( |
| 242 | 242 |
networks map[string]*composetypes.ServiceNetworkConfig, |
| 243 | 243 |
networkConfigs map[string]composetypes.NetworkConfig, |
| 244 |
- namespace namespace, |
|
| 244 |
+ namespace composetransform.Namespace, |
|
| 245 | 245 |
name string, |
| 246 | 246 |
) ([]swarm.NetworkAttachmentConfig, error) {
|
| 247 | 247 |
if len(networks) == 0 {
|
| ... | ... |
@@ -275,116 +274,6 @@ func convertServiceNetworks( |
| 275 | 275 |
return nets, nil |
| 276 | 276 |
} |
| 277 | 277 |
|
| 278 |
-func convertVolumes( |
|
| 279 |
- serviceVolumes []string, |
|
| 280 |
- stackVolumes map[string]composetypes.VolumeConfig, |
|
| 281 |
- namespace namespace, |
|
| 282 |
-) ([]mount.Mount, error) {
|
|
| 283 |
- var mounts []mount.Mount |
|
| 284 |
- |
|
| 285 |
- for _, volumeSpec := range serviceVolumes {
|
|
| 286 |
- mount, err := convertVolumeToMount(volumeSpec, stackVolumes, namespace) |
|
| 287 |
- if err != nil {
|
|
| 288 |
- return nil, err |
|
| 289 |
- } |
|
| 290 |
- mounts = append(mounts, mount) |
|
| 291 |
- } |
|
| 292 |
- return mounts, nil |
|
| 293 |
-} |
|
| 294 |
- |
|
| 295 |
-func convertVolumeToMount( |
|
| 296 |
- volumeSpec string, |
|
| 297 |
- stackVolumes map[string]composetypes.VolumeConfig, |
|
| 298 |
- namespace namespace, |
|
| 299 |
-) (mount.Mount, error) {
|
|
| 300 |
- var source, target string |
|
| 301 |
- var mode []string |
|
| 302 |
- |
|
| 303 |
- // TODO: split Windows path mappings properly |
|
| 304 |
- parts := strings.SplitN(volumeSpec, ":", 3) |
|
| 305 |
- |
|
| 306 |
- switch len(parts) {
|
|
| 307 |
- case 3: |
|
| 308 |
- source = parts[0] |
|
| 309 |
- target = parts[1] |
|
| 310 |
- mode = strings.Split(parts[2], ",") |
|
| 311 |
- case 2: |
|
| 312 |
- source = parts[0] |
|
| 313 |
- target = parts[1] |
|
| 314 |
- case 1: |
|
| 315 |
- target = parts[0] |
|
| 316 |
- default: |
|
| 317 |
- return mount.Mount{}, fmt.Errorf("invald volume: %s", volumeSpec)
|
|
| 318 |
- } |
|
| 319 |
- |
|
| 320 |
- // TODO: catch Windows paths here |
|
| 321 |
- if strings.HasPrefix(source, "/") {
|
|
| 322 |
- return mount.Mount{
|
|
| 323 |
- Type: mount.TypeBind, |
|
| 324 |
- Source: source, |
|
| 325 |
- Target: target, |
|
| 326 |
- ReadOnly: isReadOnly(mode), |
|
| 327 |
- BindOptions: getBindOptions(mode), |
|
| 328 |
- }, nil |
|
| 329 |
- } |
|
| 330 |
- |
|
| 331 |
- stackVolume, exists := stackVolumes[source] |
|
| 332 |
- if !exists {
|
|
| 333 |
- return mount.Mount{}, fmt.Errorf("undefined volume: %s", source)
|
|
| 334 |
- } |
|
| 335 |
- |
|
| 336 |
- var volumeOptions *mount.VolumeOptions |
|
| 337 |
- if stackVolume.External.Name != "" {
|
|
| 338 |
- source = stackVolume.External.Name |
|
| 339 |
- } else {
|
|
| 340 |
- volumeOptions = &mount.VolumeOptions{
|
|
| 341 |
- Labels: getStackLabels(namespace.name, stackVolume.Labels), |
|
| 342 |
- NoCopy: isNoCopy(mode), |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- if stackVolume.Driver != "" {
|
|
| 346 |
- volumeOptions.DriverConfig = &mount.Driver{
|
|
| 347 |
- Name: stackVolume.Driver, |
|
| 348 |
- Options: stackVolume.DriverOpts, |
|
| 349 |
- } |
|
| 350 |
- } |
|
| 351 |
- source = namespace.scope(source) |
|
| 352 |
- } |
|
| 353 |
- return mount.Mount{
|
|
| 354 |
- Type: mount.TypeVolume, |
|
| 355 |
- Source: source, |
|
| 356 |
- Target: target, |
|
| 357 |
- ReadOnly: isReadOnly(mode), |
|
| 358 |
- VolumeOptions: volumeOptions, |
|
| 359 |
- }, nil |
|
| 360 |
-} |
|
| 361 |
- |
|
| 362 |
-func modeHas(mode []string, field string) bool {
|
|
| 363 |
- for _, item := range mode {
|
|
| 364 |
- if item == field {
|
|
| 365 |
- return true |
|
| 366 |
- } |
|
| 367 |
- } |
|
| 368 |
- return false |
|
| 369 |
-} |
|
| 370 |
- |
|
| 371 |
-func isReadOnly(mode []string) bool {
|
|
| 372 |
- return modeHas(mode, "ro") |
|
| 373 |
-} |
|
| 374 |
- |
|
| 375 |
-func isNoCopy(mode []string) bool {
|
|
| 376 |
- return modeHas(mode, "nocopy") |
|
| 377 |
-} |
|
| 378 |
- |
|
| 379 |
-func getBindOptions(mode []string) *mount.BindOptions {
|
|
| 380 |
- for _, item := range mode {
|
|
| 381 |
- if strings.Contains(item, "private") || strings.Contains(item, "shared") || strings.Contains(item, "slave") {
|
|
| 382 |
- return &mount.BindOptions{Propagation: mount.Propagation(item)}
|
|
| 383 |
- } |
|
| 384 |
- } |
|
| 385 |
- return nil |
|
| 386 |
-} |
|
| 387 |
- |
|
| 388 | 278 |
func deployServices( |
| 389 | 279 |
ctx context.Context, |
| 390 | 280 |
dockerCli *command.DockerCli, |
| ... | ... |
@@ -494,7 +383,7 @@ func convertService( |
| 494 | 494 |
return swarm.ServiceSpec{}, err
|
| 495 | 495 |
} |
| 496 | 496 |
|
| 497 |
- mounts, err := convertVolumes(service.Volumes, volumes, namespace) |
|
| 497 |
+ mounts, err := composetransform.ConvertVolumes(service.Volumes, volumes, namespace) |
|
| 498 | 498 |
if err != nil {
|
| 499 | 499 |
// TODO: better error message (include service name) |
| 500 | 500 |
return swarm.ServiceSpec{}, err
|
| ... | ... |
@@ -7,7 +7,8 @@ import ( |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
const ( |
| 10 |
- labelNamespace = "com.docker.stack.namespace" |
|
| 10 |
+ // LabelNamespace is the label used to track stack resources |
|
| 11 |
+ LabelNamespace = "com.docker.stack.namespace" |
|
| 11 | 12 |
) |
| 12 | 13 |
|
| 13 | 14 |
// Namespace mangles names by prepending the name |
| ... | ... |
@@ -20,12 +21,22 @@ func (n Namespace) Scope(name string) string {
|
| 20 | 20 |
return n.name + "_" + name |
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 |
+// Name returns the name of the namespace |
|
| 24 |
+func (n Namespace) Name() string {
|
|
| 25 |
+ return n.name |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// NewNamespace returns a new Namespace for scoping of names |
|
| 29 |
+func NewNamespace(name string) Namespace {
|
|
| 30 |
+ return Namespace{name: name}
|
|
| 31 |
+} |
|
| 32 |
+ |
|
| 23 | 33 |
// AddStackLabel returns labels with the namespace label added |
| 24 | 34 |
func AddStackLabel(namespace Namespace, labels map[string]string) map[string]string {
|
| 25 | 35 |
if labels == nil {
|
| 26 | 36 |
labels = make(map[string]string) |
| 27 | 37 |
} |
| 28 |
- labels[labelNamespace] = namespace.name |
|
| 38 |
+ labels[LabelNamespace] = namespace.name |
|
| 29 | 39 |
return labels |
| 30 | 40 |
} |
| 31 | 41 |
|
| ... | ... |
@@ -21,7 +21,7 @@ func TestAddStackLabel(t *testing.T) {
|
| 21 | 21 |
actual := AddStackLabel(Namespace{name: "foo"}, labels)
|
| 22 | 22 |
expected := map[string]string{
|
| 23 | 23 |
"something": "labeled", |
| 24 |
- labelNamespace: "foo", |
|
| 24 |
+ LabelNamespace: "foo", |
|
| 25 | 25 |
} |
| 26 | 26 |
assert.DeepEqual(t, actual, expected) |
| 27 | 27 |
} |
| ... | ... |
@@ -56,7 +56,7 @@ func TestConvertNetworks(t *testing.T) {
|
| 56 | 56 |
expected := map[string]types.NetworkCreate{
|
| 57 | 57 |
"default": {
|
| 58 | 58 |
Labels: map[string]string{
|
| 59 |
- labelNamespace: "foo", |
|
| 59 |
+ LabelNamespace: "foo", |
|
| 60 | 60 |
}, |
| 61 | 61 |
}, |
| 62 | 62 |
"normal": {
|
| ... | ... |
@@ -73,7 +73,7 @@ func TestConvertNetworks(t *testing.T) {
|
| 73 | 73 |
"opt": "value", |
| 74 | 74 |
}, |
| 75 | 75 |
Labels: map[string]string{
|
| 76 |
- labelNamespace: "foo", |
|
| 76 |
+ LabelNamespace: "foo", |
|
| 77 | 77 |
"something": "labeled", |
| 78 | 78 |
}, |
| 79 | 79 |
}, |
| 80 | 80 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,116 @@ |
| 0 |
+package composetransform |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 |
+ composetypes "github.com/aanand/compose-file/types" |
|
| 7 |
+ "github.com/docker/docker/api/types/mount" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type volumes map[string]composetypes.VolumeConfig |
|
| 11 |
+ |
|
| 12 |
+// ConvertVolumes from compose-file types to engine api types |
|
| 13 |
+func ConvertVolumes(serviceVolumes []string, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) {
|
|
| 14 |
+ var mounts []mount.Mount |
|
| 15 |
+ |
|
| 16 |
+ for _, volumeSpec := range serviceVolumes {
|
|
| 17 |
+ mount, err := convertVolumeToMount(volumeSpec, stackVolumes, namespace) |
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return nil, err |
|
| 20 |
+ } |
|
| 21 |
+ mounts = append(mounts, mount) |
|
| 22 |
+ } |
|
| 23 |
+ return mounts, nil |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func convertVolumeToMount(volumeSpec string, stackVolumes volumes, namespace Namespace) (mount.Mount, error) {
|
|
| 27 |
+ var source, target string |
|
| 28 |
+ var mode []string |
|
| 29 |
+ |
|
| 30 |
+ // TODO: split Windows path mappings properly |
|
| 31 |
+ parts := strings.SplitN(volumeSpec, ":", 3) |
|
| 32 |
+ |
|
| 33 |
+ switch len(parts) {
|
|
| 34 |
+ case 3: |
|
| 35 |
+ source = parts[0] |
|
| 36 |
+ target = parts[1] |
|
| 37 |
+ mode = strings.Split(parts[2], ",") |
|
| 38 |
+ case 2: |
|
| 39 |
+ source = parts[0] |
|
| 40 |
+ target = parts[1] |
|
| 41 |
+ case 1: |
|
| 42 |
+ target = parts[0] |
|
| 43 |
+ default: |
|
| 44 |
+ return mount.Mount{}, fmt.Errorf("invald volume: %s", volumeSpec)
|
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ // TODO: catch Windows paths here |
|
| 48 |
+ if strings.HasPrefix(source, "/") {
|
|
| 49 |
+ return mount.Mount{
|
|
| 50 |
+ Type: mount.TypeBind, |
|
| 51 |
+ Source: source, |
|
| 52 |
+ Target: target, |
|
| 53 |
+ ReadOnly: isReadOnly(mode), |
|
| 54 |
+ BindOptions: getBindOptions(mode), |
|
| 55 |
+ }, nil |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ stackVolume, exists := stackVolumes[source] |
|
| 59 |
+ if !exists {
|
|
| 60 |
+ return mount.Mount{}, fmt.Errorf("undefined volume: %s", source)
|
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ var volumeOptions *mount.VolumeOptions |
|
| 64 |
+ if stackVolume.External.Name != "" {
|
|
| 65 |
+ source = stackVolume.External.Name |
|
| 66 |
+ } else {
|
|
| 67 |
+ volumeOptions = &mount.VolumeOptions{
|
|
| 68 |
+ Labels: AddStackLabel(namespace, stackVolume.Labels), |
|
| 69 |
+ NoCopy: isNoCopy(mode), |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ if stackVolume.Driver != "" {
|
|
| 73 |
+ volumeOptions.DriverConfig = &mount.Driver{
|
|
| 74 |
+ Name: stackVolume.Driver, |
|
| 75 |
+ Options: stackVolume.DriverOpts, |
|
| 76 |
+ } |
|
| 77 |
+ } |
|
| 78 |
+ source = namespace.Scope(source) |
|
| 79 |
+ } |
|
| 80 |
+ return mount.Mount{
|
|
| 81 |
+ Type: mount.TypeVolume, |
|
| 82 |
+ Source: source, |
|
| 83 |
+ Target: target, |
|
| 84 |
+ ReadOnly: isReadOnly(mode), |
|
| 85 |
+ VolumeOptions: volumeOptions, |
|
| 86 |
+ }, nil |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func modeHas(mode []string, field string) bool {
|
|
| 90 |
+ for _, item := range mode {
|
|
| 91 |
+ if item == field {
|
|
| 92 |
+ return true |
|
| 93 |
+ } |
|
| 94 |
+ } |
|
| 95 |
+ return false |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+func isReadOnly(mode []string) bool {
|
|
| 99 |
+ return modeHas(mode, "ro") |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func isNoCopy(mode []string) bool {
|
|
| 103 |
+ return modeHas(mode, "nocopy") |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+func getBindOptions(mode []string) *mount.BindOptions {
|
|
| 107 |
+ for _, item := range mode {
|
|
| 108 |
+ for _, propagation := range mount.Propagations {
|
|
| 109 |
+ if mount.Propagation(item) == propagation {
|
|
| 110 |
+ return &mount.BindOptions{Propagation: mount.Propagation(item)}
|
|
| 111 |
+ } |
|
| 112 |
+ } |
|
| 113 |
+ } |
|
| 114 |
+ return nil |
|
| 115 |
+} |
| 0 | 116 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,112 @@ |
| 0 |
+package composetransform |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+ |
|
| 5 |
+ composetypes "github.com/aanand/compose-file/types" |
|
| 6 |
+ "github.com/docker/docker/api/types/mount" |
|
| 7 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestIsReadOnly(t *testing.T) {
|
|
| 11 |
+ assert.Equal(t, isReadOnly([]string{"foo", "bar", "ro"}), true)
|
|
| 12 |
+ assert.Equal(t, isReadOnly([]string{"ro"}), true)
|
|
| 13 |
+ assert.Equal(t, isReadOnly([]string{}), false)
|
|
| 14 |
+ assert.Equal(t, isReadOnly([]string{"foo", "rw"}), false)
|
|
| 15 |
+ assert.Equal(t, isReadOnly([]string{"foo"}), false)
|
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func TestIsNoCopy(t *testing.T) {
|
|
| 19 |
+ assert.Equal(t, isNoCopy([]string{"foo", "bar", "nocopy"}), true)
|
|
| 20 |
+ assert.Equal(t, isNoCopy([]string{"nocopy"}), true)
|
|
| 21 |
+ assert.Equal(t, isNoCopy([]string{}), false)
|
|
| 22 |
+ assert.Equal(t, isNoCopy([]string{"foo", "rw"}), false)
|
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func TesTGetBindOptions(t *testing.T) {
|
|
| 26 |
+ opts := getBindOptions([]string{"slave"})
|
|
| 27 |
+ expected := &mount.BindOptions{Propagation: mount.PropagationSlave}
|
|
| 28 |
+ assert.Equal(t, opts, expected) |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+func TesTGetBindOptionsNone(t *testing.T) {
|
|
| 32 |
+ opts := getBindOptions([]string{"ro"})
|
|
| 33 |
+ assert.Equal(t, opts, nil) |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func TestConvertVolumeToMountNamedVolume(t *testing.T) {
|
|
| 37 |
+ stackVolumes := volumes{
|
|
| 38 |
+ "normal": composetypes.VolumeConfig{
|
|
| 39 |
+ Driver: "glusterfs", |
|
| 40 |
+ DriverOpts: map[string]string{
|
|
| 41 |
+ "opt": "value", |
|
| 42 |
+ }, |
|
| 43 |
+ Labels: map[string]string{
|
|
| 44 |
+ "something": "labeled", |
|
| 45 |
+ }, |
|
| 46 |
+ }, |
|
| 47 |
+ } |
|
| 48 |
+ namespace := NewNamespace("foo")
|
|
| 49 |
+ expected := mount.Mount{
|
|
| 50 |
+ Type: mount.TypeVolume, |
|
| 51 |
+ Source: "foo_normal", |
|
| 52 |
+ Target: "/foo", |
|
| 53 |
+ ReadOnly: true, |
|
| 54 |
+ VolumeOptions: &mount.VolumeOptions{
|
|
| 55 |
+ Labels: map[string]string{
|
|
| 56 |
+ LabelNamespace: "foo", |
|
| 57 |
+ "something": "labeled", |
|
| 58 |
+ }, |
|
| 59 |
+ DriverConfig: &mount.Driver{
|
|
| 60 |
+ Name: "glusterfs", |
|
| 61 |
+ Options: map[string]string{
|
|
| 62 |
+ "opt": "value", |
|
| 63 |
+ }, |
|
| 64 |
+ }, |
|
| 65 |
+ }, |
|
| 66 |
+ } |
|
| 67 |
+ mount, err := convertVolumeToMount("normal:/foo:ro", stackVolumes, namespace)
|
|
| 68 |
+ assert.NilError(t, err) |
|
| 69 |
+ assert.DeepEqual(t, mount, expected) |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+func TestConvertVolumeToMountNamedVolumeExternal(t *testing.T) {
|
|
| 73 |
+ stackVolumes := volumes{
|
|
| 74 |
+ "outside": composetypes.VolumeConfig{
|
|
| 75 |
+ External: composetypes.External{
|
|
| 76 |
+ External: true, |
|
| 77 |
+ Name: "special", |
|
| 78 |
+ }, |
|
| 79 |
+ }, |
|
| 80 |
+ } |
|
| 81 |
+ namespace := NewNamespace("foo")
|
|
| 82 |
+ expected := mount.Mount{
|
|
| 83 |
+ Type: mount.TypeVolume, |
|
| 84 |
+ Source: "special", |
|
| 85 |
+ Target: "/foo", |
|
| 86 |
+ } |
|
| 87 |
+ mount, err := convertVolumeToMount("outside:/foo", stackVolumes, namespace)
|
|
| 88 |
+ assert.NilError(t, err) |
|
| 89 |
+ assert.DeepEqual(t, mount, expected) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func TestConvertVolumeToMountBind(t *testing.T) {
|
|
| 93 |
+ stackVolumes := volumes{}
|
|
| 94 |
+ namespace := NewNamespace("foo")
|
|
| 95 |
+ expected := mount.Mount{
|
|
| 96 |
+ Type: mount.TypeBind, |
|
| 97 |
+ Source: "/bar", |
|
| 98 |
+ Target: "/foo", |
|
| 99 |
+ ReadOnly: true, |
|
| 100 |
+ BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared},
|
|
| 101 |
+ } |
|
| 102 |
+ mount, err := convertVolumeToMount("/bar:/foo:ro,shared", stackVolumes, namespace)
|
|
| 103 |
+ assert.NilError(t, err) |
|
| 104 |
+ assert.DeepEqual(t, mount, expected) |
|
| 105 |
+} |
|
| 106 |
+ |
|
| 107 |
+func TestConvertVolumeToMountVolumeDoesNotExist(t *testing.T) {
|
|
| 108 |
+ namespace := NewNamespace("foo")
|
|
| 109 |
+ _, err := convertVolumeToMount("unknown:/foo:ro", volumes{}, namespace)
|
|
| 110 |
+ assert.Error(t, err, "undefined volume: unknown") |
|
| 111 |
+} |