loader_test.go 23 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. package loader
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "sort"
  7. "testing"
  8. "time"
  9. "github.com/docker/docker/cli/compose/types"
  10. "github.com/stretchr/testify/assert"
  11. )
  12. func buildConfigDetails(source map[string]interface{}, env map[string]string) types.ConfigDetails {
  13. workingDir, err := os.Getwd()
  14. if err != nil {
  15. panic(err)
  16. }
  17. return types.ConfigDetails{
  18. WorkingDir: workingDir,
  19. ConfigFiles: []types.ConfigFile{
  20. {Filename: "filename.yml", Config: source},
  21. },
  22. Environment: env,
  23. }
  24. }
  25. func loadYAML(yaml string) (*types.Config, error) {
  26. return loadYAMLWithEnv(yaml, nil)
  27. }
  28. func loadYAMLWithEnv(yaml string, env map[string]string) (*types.Config, error) {
  29. dict, err := ParseYAML([]byte(yaml))
  30. if err != nil {
  31. return nil, err
  32. }
  33. return Load(buildConfigDetails(dict, env))
  34. }
  35. var sampleYAML = `
  36. version: "3"
  37. services:
  38. foo:
  39. image: busybox
  40. networks:
  41. with_me:
  42. bar:
  43. image: busybox
  44. environment:
  45. - FOO=1
  46. networks:
  47. - with_ipam
  48. volumes:
  49. hello:
  50. driver: default
  51. driver_opts:
  52. beep: boop
  53. networks:
  54. default:
  55. driver: bridge
  56. driver_opts:
  57. beep: boop
  58. with_ipam:
  59. ipam:
  60. driver: default
  61. config:
  62. - subnet: 172.28.0.0/16
  63. `
  64. var sampleDict = map[string]interface{}{
  65. "version": "3",
  66. "services": map[string]interface{}{
  67. "foo": map[string]interface{}{
  68. "image": "busybox",
  69. "networks": map[string]interface{}{"with_me": nil},
  70. },
  71. "bar": map[string]interface{}{
  72. "image": "busybox",
  73. "environment": []interface{}{"FOO=1"},
  74. "networks": []interface{}{"with_ipam"},
  75. },
  76. },
  77. "volumes": map[string]interface{}{
  78. "hello": map[string]interface{}{
  79. "driver": "default",
  80. "driver_opts": map[string]interface{}{
  81. "beep": "boop",
  82. },
  83. },
  84. },
  85. "networks": map[string]interface{}{
  86. "default": map[string]interface{}{
  87. "driver": "bridge",
  88. "driver_opts": map[string]interface{}{
  89. "beep": "boop",
  90. },
  91. },
  92. "with_ipam": map[string]interface{}{
  93. "ipam": map[string]interface{}{
  94. "driver": "default",
  95. "config": []interface{}{
  96. map[string]interface{}{
  97. "subnet": "172.28.0.0/16",
  98. },
  99. },
  100. },
  101. },
  102. },
  103. }
  104. func strPtr(val string) *string {
  105. return &val
  106. }
  107. var sampleConfig = types.Config{
  108. Services: []types.ServiceConfig{
  109. {
  110. Name: "foo",
  111. Image: "busybox",
  112. Environment: map[string]*string{},
  113. Networks: map[string]*types.ServiceNetworkConfig{
  114. "with_me": nil,
  115. },
  116. },
  117. {
  118. Name: "bar",
  119. Image: "busybox",
  120. Environment: map[string]*string{"FOO": strPtr("1")},
  121. Networks: map[string]*types.ServiceNetworkConfig{
  122. "with_ipam": nil,
  123. },
  124. },
  125. },
  126. Networks: map[string]types.NetworkConfig{
  127. "default": {
  128. Driver: "bridge",
  129. DriverOpts: map[string]string{
  130. "beep": "boop",
  131. },
  132. },
  133. "with_ipam": {
  134. Ipam: types.IPAMConfig{
  135. Driver: "default",
  136. Config: []*types.IPAMPool{
  137. {
  138. Subnet: "172.28.0.0/16",
  139. },
  140. },
  141. },
  142. },
  143. },
  144. Volumes: map[string]types.VolumeConfig{
  145. "hello": {
  146. Driver: "default",
  147. DriverOpts: map[string]string{
  148. "beep": "boop",
  149. },
  150. },
  151. },
  152. }
  153. func TestParseYAML(t *testing.T) {
  154. dict, err := ParseYAML([]byte(sampleYAML))
  155. if !assert.NoError(t, err) {
  156. return
  157. }
  158. assert.Equal(t, sampleDict, dict)
  159. }
  160. func TestLoad(t *testing.T) {
  161. actual, err := Load(buildConfigDetails(sampleDict, nil))
  162. if !assert.NoError(t, err) {
  163. return
  164. }
  165. assert.Equal(t, serviceSort(sampleConfig.Services), serviceSort(actual.Services))
  166. assert.Equal(t, sampleConfig.Networks, actual.Networks)
  167. assert.Equal(t, sampleConfig.Volumes, actual.Volumes)
  168. }
  169. func TestLoadV31(t *testing.T) {
  170. actual, err := loadYAML(`
  171. version: "3.1"
  172. services:
  173. foo:
  174. image: busybox
  175. secrets: [super]
  176. secrets:
  177. super:
  178. external: true
  179. `)
  180. if !assert.NoError(t, err) {
  181. return
  182. }
  183. assert.Equal(t, len(actual.Services), 1)
  184. assert.Equal(t, len(actual.Secrets), 1)
  185. }
  186. func TestParseAndLoad(t *testing.T) {
  187. actual, err := loadYAML(sampleYAML)
  188. if !assert.NoError(t, err) {
  189. return
  190. }
  191. assert.Equal(t, serviceSort(sampleConfig.Services), serviceSort(actual.Services))
  192. assert.Equal(t, sampleConfig.Networks, actual.Networks)
  193. assert.Equal(t, sampleConfig.Volumes, actual.Volumes)
  194. }
  195. func TestInvalidTopLevelObjectType(t *testing.T) {
  196. _, err := loadYAML("1")
  197. assert.Error(t, err)
  198. assert.Contains(t, err.Error(), "Top-level object must be a mapping")
  199. _, err = loadYAML("\"hello\"")
  200. assert.Error(t, err)
  201. assert.Contains(t, err.Error(), "Top-level object must be a mapping")
  202. _, err = loadYAML("[\"hello\"]")
  203. assert.Error(t, err)
  204. assert.Contains(t, err.Error(), "Top-level object must be a mapping")
  205. }
  206. func TestNonStringKeys(t *testing.T) {
  207. _, err := loadYAML(`
  208. version: "3"
  209. 123:
  210. foo:
  211. image: busybox
  212. `)
  213. assert.Error(t, err)
  214. assert.Contains(t, err.Error(), "Non-string key at top level: 123")
  215. _, err = loadYAML(`
  216. version: "3"
  217. services:
  218. foo:
  219. image: busybox
  220. 123:
  221. image: busybox
  222. `)
  223. assert.Error(t, err)
  224. assert.Contains(t, err.Error(), "Non-string key in services: 123")
  225. _, err = loadYAML(`
  226. version: "3"
  227. services:
  228. foo:
  229. image: busybox
  230. networks:
  231. default:
  232. ipam:
  233. config:
  234. - 123: oh dear
  235. `)
  236. assert.Error(t, err)
  237. assert.Contains(t, err.Error(), "Non-string key in networks.default.ipam.config[0]: 123")
  238. _, err = loadYAML(`
  239. version: "3"
  240. services:
  241. dict-env:
  242. image: busybox
  243. environment:
  244. 1: FOO
  245. `)
  246. assert.Error(t, err)
  247. assert.Contains(t, err.Error(), "Non-string key in services.dict-env.environment: 1")
  248. }
  249. func TestSupportedVersion(t *testing.T) {
  250. _, err := loadYAML(`
  251. version: "3"
  252. services:
  253. foo:
  254. image: busybox
  255. `)
  256. assert.NoError(t, err)
  257. _, err = loadYAML(`
  258. version: "3.0"
  259. services:
  260. foo:
  261. image: busybox
  262. `)
  263. assert.NoError(t, err)
  264. }
  265. func TestUnsupportedVersion(t *testing.T) {
  266. _, err := loadYAML(`
  267. version: "2"
  268. services:
  269. foo:
  270. image: busybox
  271. `)
  272. assert.Error(t, err)
  273. assert.Contains(t, err.Error(), "version")
  274. _, err = loadYAML(`
  275. version: "2.0"
  276. services:
  277. foo:
  278. image: busybox
  279. `)
  280. assert.Error(t, err)
  281. assert.Contains(t, err.Error(), "version")
  282. }
  283. func TestInvalidVersion(t *testing.T) {
  284. _, err := loadYAML(`
  285. version: 3
  286. services:
  287. foo:
  288. image: busybox
  289. `)
  290. assert.Error(t, err)
  291. assert.Contains(t, err.Error(), "version must be a string")
  292. }
  293. func TestV1Unsupported(t *testing.T) {
  294. _, err := loadYAML(`
  295. foo:
  296. image: busybox
  297. `)
  298. assert.Error(t, err)
  299. }
  300. func TestNonMappingObject(t *testing.T) {
  301. _, err := loadYAML(`
  302. version: "3"
  303. services:
  304. - foo:
  305. image: busybox
  306. `)
  307. assert.Error(t, err)
  308. assert.Contains(t, err.Error(), "services must be a mapping")
  309. _, err = loadYAML(`
  310. version: "3"
  311. services:
  312. foo: busybox
  313. `)
  314. assert.Error(t, err)
  315. assert.Contains(t, err.Error(), "services.foo must be a mapping")
  316. _, err = loadYAML(`
  317. version: "3"
  318. networks:
  319. - default:
  320. driver: bridge
  321. `)
  322. assert.Error(t, err)
  323. assert.Contains(t, err.Error(), "networks must be a mapping")
  324. _, err = loadYAML(`
  325. version: "3"
  326. networks:
  327. default: bridge
  328. `)
  329. assert.Error(t, err)
  330. assert.Contains(t, err.Error(), "networks.default must be a mapping")
  331. _, err = loadYAML(`
  332. version: "3"
  333. volumes:
  334. - data:
  335. driver: local
  336. `)
  337. assert.Error(t, err)
  338. assert.Contains(t, err.Error(), "volumes must be a mapping")
  339. _, err = loadYAML(`
  340. version: "3"
  341. volumes:
  342. data: local
  343. `)
  344. assert.Error(t, err)
  345. assert.Contains(t, err.Error(), "volumes.data must be a mapping")
  346. }
  347. func TestNonStringImage(t *testing.T) {
  348. _, err := loadYAML(`
  349. version: "3"
  350. services:
  351. foo:
  352. image: ["busybox", "latest"]
  353. `)
  354. assert.Error(t, err)
  355. assert.Contains(t, err.Error(), "services.foo.image must be a string")
  356. }
  357. func TestLoadWithEnvironment(t *testing.T) {
  358. config, err := loadYAMLWithEnv(`
  359. version: "3"
  360. services:
  361. dict-env:
  362. image: busybox
  363. environment:
  364. FOO: "1"
  365. BAR: 2
  366. BAZ: 2.5
  367. QUX:
  368. QUUX:
  369. list-env:
  370. image: busybox
  371. environment:
  372. - FOO=1
  373. - BAR=2
  374. - BAZ=2.5
  375. - QUX=
  376. - QUUX
  377. `, map[string]string{"QUX": "qux"})
  378. assert.NoError(t, err)
  379. expected := types.MappingWithEquals{
  380. "FOO": strPtr("1"),
  381. "BAR": strPtr("2"),
  382. "BAZ": strPtr("2.5"),
  383. "QUX": strPtr("qux"),
  384. "QUUX": nil,
  385. }
  386. assert.Equal(t, 2, len(config.Services))
  387. for _, service := range config.Services {
  388. assert.Equal(t, expected, service.Environment)
  389. }
  390. }
  391. func TestInvalidEnvironmentValue(t *testing.T) {
  392. _, err := loadYAML(`
  393. version: "3"
  394. services:
  395. dict-env:
  396. image: busybox
  397. environment:
  398. FOO: ["1"]
  399. `)
  400. assert.Error(t, err)
  401. assert.Contains(t, err.Error(), "services.dict-env.environment.FOO must be a string, number or null")
  402. }
  403. func TestInvalidEnvironmentObject(t *testing.T) {
  404. _, err := loadYAML(`
  405. version: "3"
  406. services:
  407. dict-env:
  408. image: busybox
  409. environment: "FOO=1"
  410. `)
  411. assert.Error(t, err)
  412. assert.Contains(t, err.Error(), "services.dict-env.environment must be a mapping")
  413. }
  414. func TestEnvironmentInterpolation(t *testing.T) {
  415. home := "/home/foo"
  416. config, err := loadYAMLWithEnv(`
  417. version: "3"
  418. services:
  419. test:
  420. image: busybox
  421. labels:
  422. - home1=$HOME
  423. - home2=${HOME}
  424. - nonexistent=$NONEXISTENT
  425. - default=${NONEXISTENT-default}
  426. networks:
  427. test:
  428. driver: $HOME
  429. volumes:
  430. test:
  431. driver: $HOME
  432. `, map[string]string{
  433. "HOME": home,
  434. "FOO": "foo",
  435. })
  436. assert.NoError(t, err)
  437. expectedLabels := types.Labels{
  438. "home1": home,
  439. "home2": home,
  440. "nonexistent": "",
  441. "default": "default",
  442. }
  443. assert.Equal(t, expectedLabels, config.Services[0].Labels)
  444. assert.Equal(t, home, config.Networks["test"].Driver)
  445. assert.Equal(t, home, config.Volumes["test"].Driver)
  446. }
  447. func TestUnsupportedProperties(t *testing.T) {
  448. dict, err := ParseYAML([]byte(`
  449. version: "3"
  450. services:
  451. web:
  452. image: web
  453. build: ./web
  454. links:
  455. - bar
  456. db:
  457. image: db
  458. build: ./db
  459. `))
  460. assert.NoError(t, err)
  461. configDetails := buildConfigDetails(dict, nil)
  462. _, err = Load(configDetails)
  463. assert.NoError(t, err)
  464. unsupported := GetUnsupportedProperties(configDetails)
  465. assert.Equal(t, []string{"build", "links"}, unsupported)
  466. }
  467. func TestDeprecatedProperties(t *testing.T) {
  468. dict, err := ParseYAML([]byte(`
  469. version: "3"
  470. services:
  471. web:
  472. image: web
  473. container_name: web
  474. db:
  475. image: db
  476. container_name: db
  477. expose: ["5434"]
  478. `))
  479. assert.NoError(t, err)
  480. configDetails := buildConfigDetails(dict, nil)
  481. _, err = Load(configDetails)
  482. assert.NoError(t, err)
  483. deprecated := GetDeprecatedProperties(configDetails)
  484. assert.Equal(t, 2, len(deprecated))
  485. assert.Contains(t, deprecated, "container_name")
  486. assert.Contains(t, deprecated, "expose")
  487. }
  488. func TestForbiddenProperties(t *testing.T) {
  489. _, err := loadYAML(`
  490. version: "3"
  491. services:
  492. foo:
  493. image: busybox
  494. volumes:
  495. - /data
  496. volume_driver: some-driver
  497. bar:
  498. extends:
  499. service: foo
  500. `)
  501. assert.Error(t, err)
  502. assert.IsType(t, &ForbiddenPropertiesError{}, err)
  503. fmt.Println(err)
  504. forbidden := err.(*ForbiddenPropertiesError).Properties
  505. assert.Equal(t, 2, len(forbidden))
  506. assert.Contains(t, forbidden, "volume_driver")
  507. assert.Contains(t, forbidden, "extends")
  508. }
  509. func TestInvalidExternalAndDriverCombination(t *testing.T) {
  510. _, err := loadYAML(`
  511. version: "3"
  512. volumes:
  513. external_volume:
  514. external: true
  515. driver: foobar
  516. `)
  517. assert.Error(t, err)
  518. assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"driver\" specified for volume")
  519. assert.Contains(t, err.Error(), "external_volume")
  520. }
  521. func TestInvalidExternalAndDirverOptsCombination(t *testing.T) {
  522. _, err := loadYAML(`
  523. version: "3"
  524. volumes:
  525. external_volume:
  526. external: true
  527. driver_opts:
  528. beep: boop
  529. `)
  530. assert.Error(t, err)
  531. assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"driver_opts\" specified for volume")
  532. assert.Contains(t, err.Error(), "external_volume")
  533. }
  534. func TestInvalidExternalAndLabelsCombination(t *testing.T) {
  535. _, err := loadYAML(`
  536. version: "3"
  537. volumes:
  538. external_volume:
  539. external: true
  540. labels:
  541. - beep=boop
  542. `)
  543. assert.Error(t, err)
  544. assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"labels\" specified for volume")
  545. assert.Contains(t, err.Error(), "external_volume")
  546. }
  547. func durationPtr(value time.Duration) *time.Duration {
  548. return &value
  549. }
  550. func int64Ptr(value int64) *int64 {
  551. return &value
  552. }
  553. func uint64Ptr(value uint64) *uint64 {
  554. return &value
  555. }
  556. func TestFullExample(t *testing.T) {
  557. bytes, err := ioutil.ReadFile("full-example.yml")
  558. assert.NoError(t, err)
  559. homeDir := "/home/foo"
  560. env := map[string]string{"HOME": homeDir, "QUX": "qux_from_environment"}
  561. config, err := loadYAMLWithEnv(string(bytes), env)
  562. if !assert.NoError(t, err) {
  563. return
  564. }
  565. workingDir, err := os.Getwd()
  566. assert.NoError(t, err)
  567. stopGracePeriod := time.Duration(20 * time.Second)
  568. expectedServiceConfig := types.ServiceConfig{
  569. Name: "foo",
  570. CapAdd: []string{"ALL"},
  571. CapDrop: []string{"NET_ADMIN", "SYS_ADMIN"},
  572. CgroupParent: "m-executor-abcd",
  573. Command: []string{"bundle", "exec", "thin", "-p", "3000"},
  574. ContainerName: "my-web-container",
  575. DependsOn: []string{"db", "redis"},
  576. Deploy: types.DeployConfig{
  577. Mode: "replicated",
  578. Replicas: uint64Ptr(6),
  579. Labels: map[string]string{"FOO": "BAR"},
  580. UpdateConfig: &types.UpdateConfig{
  581. Parallelism: uint64Ptr(3),
  582. Delay: time.Duration(10 * time.Second),
  583. FailureAction: "continue",
  584. Monitor: time.Duration(60 * time.Second),
  585. MaxFailureRatio: 0.3,
  586. },
  587. Resources: types.Resources{
  588. Limits: &types.Resource{
  589. NanoCPUs: "0.001",
  590. MemoryBytes: 50 * 1024 * 1024,
  591. },
  592. Reservations: &types.Resource{
  593. NanoCPUs: "0.0001",
  594. MemoryBytes: 20 * 1024 * 1024,
  595. },
  596. },
  597. RestartPolicy: &types.RestartPolicy{
  598. Condition: "on_failure",
  599. Delay: durationPtr(5 * time.Second),
  600. MaxAttempts: uint64Ptr(3),
  601. Window: durationPtr(2 * time.Minute),
  602. },
  603. Placement: types.Placement{
  604. Constraints: []string{"node=foo"},
  605. },
  606. EndpointMode: "dnsrr",
  607. },
  608. Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"},
  609. DNS: []string{"8.8.8.8", "9.9.9.9"},
  610. DNSSearch: []string{"dc1.example.com", "dc2.example.com"},
  611. DomainName: "foo.com",
  612. Entrypoint: []string{"/code/entrypoint.sh", "-p", "3000"},
  613. Environment: map[string]*string{
  614. "FOO": strPtr("foo_from_env_file"),
  615. "BAR": strPtr("bar_from_env_file_2"),
  616. "BAZ": strPtr("baz_from_service_def"),
  617. "QUX": strPtr("qux_from_environment"),
  618. },
  619. EnvFile: []string{
  620. "./example1.env",
  621. "./example2.env",
  622. },
  623. Expose: []string{"3000", "8000"},
  624. ExternalLinks: []string{
  625. "redis_1",
  626. "project_db_1:mysql",
  627. "project_db_1:postgresql",
  628. },
  629. ExtraHosts: map[string]string{
  630. "otherhost": "50.31.209.229",
  631. "somehost": "162.242.195.82",
  632. },
  633. HealthCheck: &types.HealthCheckConfig{
  634. Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}),
  635. Interval: "10s",
  636. Timeout: "1s",
  637. Retries: uint64Ptr(5),
  638. },
  639. Hostname: "foo",
  640. Image: "redis",
  641. Ipc: "host",
  642. Labels: map[string]string{
  643. "com.example.description": "Accounting webapp",
  644. "com.example.number": "42",
  645. "com.example.empty-label": "",
  646. },
  647. Links: []string{
  648. "db",
  649. "db:database",
  650. "redis",
  651. },
  652. Logging: &types.LoggingConfig{
  653. Driver: "syslog",
  654. Options: map[string]string{
  655. "syslog-address": "tcp://192.168.0.42:123",
  656. },
  657. },
  658. MacAddress: "02:42:ac:11:65:43",
  659. NetworkMode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b",
  660. Networks: map[string]*types.ServiceNetworkConfig{
  661. "some-network": {
  662. Aliases: []string{"alias1", "alias3"},
  663. Ipv4Address: "",
  664. Ipv6Address: "",
  665. },
  666. "other-network": {
  667. Ipv4Address: "172.16.238.10",
  668. Ipv6Address: "2001:3984:3989::10",
  669. },
  670. "other-other-network": nil,
  671. },
  672. Pid: "host",
  673. Ports: []types.ServicePortConfig{
  674. //"3000",
  675. {
  676. Mode: "ingress",
  677. Target: 3000,
  678. Protocol: "tcp",
  679. },
  680. //"3000-3005",
  681. {
  682. Mode: "ingress",
  683. Target: 3000,
  684. Protocol: "tcp",
  685. },
  686. {
  687. Mode: "ingress",
  688. Target: 3001,
  689. Protocol: "tcp",
  690. },
  691. {
  692. Mode: "ingress",
  693. Target: 3002,
  694. Protocol: "tcp",
  695. },
  696. {
  697. Mode: "ingress",
  698. Target: 3003,
  699. Protocol: "tcp",
  700. },
  701. {
  702. Mode: "ingress",
  703. Target: 3004,
  704. Protocol: "tcp",
  705. },
  706. {
  707. Mode: "ingress",
  708. Target: 3005,
  709. Protocol: "tcp",
  710. },
  711. //"8000:8000",
  712. {
  713. Mode: "ingress",
  714. Target: 8000,
  715. Published: 8000,
  716. Protocol: "tcp",
  717. },
  718. //"9090-9091:8080-8081",
  719. {
  720. Mode: "ingress",
  721. Target: 8080,
  722. Published: 9090,
  723. Protocol: "tcp",
  724. },
  725. {
  726. Mode: "ingress",
  727. Target: 8081,
  728. Published: 9091,
  729. Protocol: "tcp",
  730. },
  731. //"49100:22",
  732. {
  733. Mode: "ingress",
  734. Target: 22,
  735. Published: 49100,
  736. Protocol: "tcp",
  737. },
  738. //"127.0.0.1:8001:8001",
  739. {
  740. Mode: "ingress",
  741. Target: 8001,
  742. Published: 8001,
  743. Protocol: "tcp",
  744. },
  745. //"127.0.0.1:5000-5010:5000-5010",
  746. {
  747. Mode: "ingress",
  748. Target: 5000,
  749. Published: 5000,
  750. Protocol: "tcp",
  751. },
  752. {
  753. Mode: "ingress",
  754. Target: 5001,
  755. Published: 5001,
  756. Protocol: "tcp",
  757. },
  758. {
  759. Mode: "ingress",
  760. Target: 5002,
  761. Published: 5002,
  762. Protocol: "tcp",
  763. },
  764. {
  765. Mode: "ingress",
  766. Target: 5003,
  767. Published: 5003,
  768. Protocol: "tcp",
  769. },
  770. {
  771. Mode: "ingress",
  772. Target: 5004,
  773. Published: 5004,
  774. Protocol: "tcp",
  775. },
  776. {
  777. Mode: "ingress",
  778. Target: 5005,
  779. Published: 5005,
  780. Protocol: "tcp",
  781. },
  782. {
  783. Mode: "ingress",
  784. Target: 5006,
  785. Published: 5006,
  786. Protocol: "tcp",
  787. },
  788. {
  789. Mode: "ingress",
  790. Target: 5007,
  791. Published: 5007,
  792. Protocol: "tcp",
  793. },
  794. {
  795. Mode: "ingress",
  796. Target: 5008,
  797. Published: 5008,
  798. Protocol: "tcp",
  799. },
  800. {
  801. Mode: "ingress",
  802. Target: 5009,
  803. Published: 5009,
  804. Protocol: "tcp",
  805. },
  806. {
  807. Mode: "ingress",
  808. Target: 5010,
  809. Published: 5010,
  810. Protocol: "tcp",
  811. },
  812. },
  813. Privileged: true,
  814. ReadOnly: true,
  815. Restart: "always",
  816. SecurityOpt: []string{
  817. "label=level:s0:c100,c200",
  818. "label=type:svirt_apache_t",
  819. },
  820. StdinOpen: true,
  821. StopSignal: "SIGUSR1",
  822. StopGracePeriod: &stopGracePeriod,
  823. Tmpfs: []string{"/run", "/tmp"},
  824. Tty: true,
  825. Ulimits: map[string]*types.UlimitsConfig{
  826. "nproc": {
  827. Single: 65535,
  828. },
  829. "nofile": {
  830. Soft: 20000,
  831. Hard: 40000,
  832. },
  833. },
  834. User: "someone",
  835. Volumes: []types.ServiceVolumeConfig{
  836. {Target: "/var/lib/mysql", Type: "volume"},
  837. {Source: "/opt/data", Target: "/var/lib/mysql", Type: "bind"},
  838. {Source: workingDir, Target: "/code", Type: "bind"},
  839. {Source: workingDir + "/static", Target: "/var/www/html", Type: "bind"},
  840. {Source: homeDir + "/configs", Target: "/etc/configs/", Type: "bind", ReadOnly: true},
  841. {Source: "datavolume", Target: "/var/lib/mysql", Type: "volume"},
  842. {Source: workingDir + "/opt", Target: "/opt", Consistency: "cached", Type: "bind"},
  843. },
  844. WorkingDir: "/code",
  845. }
  846. assert.Equal(t, []types.ServiceConfig{expectedServiceConfig}, config.Services)
  847. expectedNetworkConfig := map[string]types.NetworkConfig{
  848. "some-network": {},
  849. "other-network": {
  850. Driver: "overlay",
  851. DriverOpts: map[string]string{
  852. "foo": "bar",
  853. "baz": "1",
  854. },
  855. Ipam: types.IPAMConfig{
  856. Driver: "overlay",
  857. Config: []*types.IPAMPool{
  858. {Subnet: "172.16.238.0/24"},
  859. {Subnet: "2001:3984:3989::/64"},
  860. },
  861. },
  862. },
  863. "external-network": {
  864. External: types.External{
  865. Name: "external-network",
  866. External: true,
  867. },
  868. },
  869. "other-external-network": {
  870. External: types.External{
  871. Name: "my-cool-network",
  872. External: true,
  873. },
  874. },
  875. }
  876. assert.Equal(t, expectedNetworkConfig, config.Networks)
  877. expectedVolumeConfig := map[string]types.VolumeConfig{
  878. "some-volume": {},
  879. "other-volume": {
  880. Driver: "flocker",
  881. DriverOpts: map[string]string{
  882. "foo": "bar",
  883. "baz": "1",
  884. },
  885. },
  886. "external-volume": {
  887. External: types.External{
  888. Name: "external-volume",
  889. External: true,
  890. },
  891. },
  892. "other-external-volume": {
  893. External: types.External{
  894. Name: "my-cool-volume",
  895. External: true,
  896. },
  897. },
  898. }
  899. assert.Equal(t, expectedVolumeConfig, config.Volumes)
  900. }
  901. func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
  902. sort.Sort(servicesByName(services))
  903. return services
  904. }
  905. type servicesByName []types.ServiceConfig
  906. func (sbn servicesByName) Len() int { return len(sbn) }
  907. func (sbn servicesByName) Swap(i, j int) { sbn[i], sbn[j] = sbn[j], sbn[i] }
  908. func (sbn servicesByName) Less(i, j int) bool { return sbn[i].Name < sbn[j].Name }
  909. func TestLoadAttachableNetwork(t *testing.T) {
  910. config, err := loadYAML(`
  911. version: "3.2"
  912. networks:
  913. mynet1:
  914. driver: overlay
  915. attachable: true
  916. mynet2:
  917. driver: bridge
  918. `)
  919. if !assert.NoError(t, err) {
  920. return
  921. }
  922. expected := map[string]types.NetworkConfig{
  923. "mynet1": {
  924. Driver: "overlay",
  925. Attachable: true,
  926. },
  927. "mynet2": {
  928. Driver: "bridge",
  929. Attachable: false,
  930. },
  931. }
  932. assert.Equal(t, expected, config.Networks)
  933. }
  934. func TestLoadExpandedPortFormat(t *testing.T) {
  935. config, err := loadYAML(`
  936. version: "3.2"
  937. services:
  938. web:
  939. image: busybox
  940. ports:
  941. - "80-82:8080-8082"
  942. - "90-92:8090-8092/udp"
  943. - "85:8500"
  944. - 8600
  945. - protocol: udp
  946. target: 53
  947. published: 10053
  948. - mode: host
  949. target: 22
  950. published: 10022
  951. `)
  952. if !assert.NoError(t, err) {
  953. return
  954. }
  955. expected := []types.ServicePortConfig{
  956. {
  957. Mode: "ingress",
  958. Target: 8080,
  959. Published: 80,
  960. Protocol: "tcp",
  961. },
  962. {
  963. Mode: "ingress",
  964. Target: 8081,
  965. Published: 81,
  966. Protocol: "tcp",
  967. },
  968. {
  969. Mode: "ingress",
  970. Target: 8082,
  971. Published: 82,
  972. Protocol: "tcp",
  973. },
  974. {
  975. Mode: "ingress",
  976. Target: 8090,
  977. Published: 90,
  978. Protocol: "udp",
  979. },
  980. {
  981. Mode: "ingress",
  982. Target: 8091,
  983. Published: 91,
  984. Protocol: "udp",
  985. },
  986. {
  987. Mode: "ingress",
  988. Target: 8092,
  989. Published: 92,
  990. Protocol: "udp",
  991. },
  992. {
  993. Mode: "ingress",
  994. Target: 8500,
  995. Published: 85,
  996. Protocol: "tcp",
  997. },
  998. {
  999. Mode: "ingress",
  1000. Target: 8600,
  1001. Published: 0,
  1002. Protocol: "tcp",
  1003. },
  1004. {
  1005. Target: 53,
  1006. Published: 10053,
  1007. Protocol: "udp",
  1008. },
  1009. {
  1010. Mode: "host",
  1011. Target: 22,
  1012. Published: 10022,
  1013. },
  1014. }
  1015. assert.Equal(t, 1, len(config.Services))
  1016. assert.Equal(t, expected, config.Services[0].Ports)
  1017. }
  1018. func TestLoadExpandedMountFormat(t *testing.T) {
  1019. config, err := loadYAML(`
  1020. version: "3.2"
  1021. services:
  1022. web:
  1023. image: busybox
  1024. volumes:
  1025. - type: volume
  1026. source: foo
  1027. target: /target
  1028. read_only: true
  1029. volumes:
  1030. foo: {}
  1031. `)
  1032. if !assert.NoError(t, err) {
  1033. return
  1034. }
  1035. expected := types.ServiceVolumeConfig{
  1036. Type: "volume",
  1037. Source: "foo",
  1038. Target: "/target",
  1039. ReadOnly: true,
  1040. }
  1041. assert.Equal(t, 1, len(config.Services))
  1042. assert.Equal(t, 1, len(config.Services[0].Volumes))
  1043. assert.Equal(t, expected, config.Services[0].Volumes[0])
  1044. }