loader_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134
  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. },
  843. WorkingDir: "/code",
  844. }
  845. assert.Equal(t, []types.ServiceConfig{expectedServiceConfig}, config.Services)
  846. expectedNetworkConfig := map[string]types.NetworkConfig{
  847. "some-network": {},
  848. "other-network": {
  849. Driver: "overlay",
  850. DriverOpts: map[string]string{
  851. "foo": "bar",
  852. "baz": "1",
  853. },
  854. Ipam: types.IPAMConfig{
  855. Driver: "overlay",
  856. Config: []*types.IPAMPool{
  857. {Subnet: "172.16.238.0/24"},
  858. {Subnet: "2001:3984:3989::/64"},
  859. },
  860. },
  861. },
  862. "external-network": {
  863. External: types.External{
  864. Name: "external-network",
  865. External: true,
  866. },
  867. },
  868. "other-external-network": {
  869. External: types.External{
  870. Name: "my-cool-network",
  871. External: true,
  872. },
  873. },
  874. }
  875. assert.Equal(t, expectedNetworkConfig, config.Networks)
  876. expectedVolumeConfig := map[string]types.VolumeConfig{
  877. "some-volume": {},
  878. "other-volume": {
  879. Driver: "flocker",
  880. DriverOpts: map[string]string{
  881. "foo": "bar",
  882. "baz": "1",
  883. },
  884. },
  885. "external-volume": {
  886. External: types.External{
  887. Name: "external-volume",
  888. External: true,
  889. },
  890. },
  891. "other-external-volume": {
  892. External: types.External{
  893. Name: "my-cool-volume",
  894. External: true,
  895. },
  896. },
  897. }
  898. assert.Equal(t, expectedVolumeConfig, config.Volumes)
  899. }
  900. func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
  901. sort.Sort(servicesByName(services))
  902. return services
  903. }
  904. type servicesByName []types.ServiceConfig
  905. func (sbn servicesByName) Len() int { return len(sbn) }
  906. func (sbn servicesByName) Swap(i, j int) { sbn[i], sbn[j] = sbn[j], sbn[i] }
  907. func (sbn servicesByName) Less(i, j int) bool { return sbn[i].Name < sbn[j].Name }
  908. func TestLoadAttachableNetwork(t *testing.T) {
  909. config, err := loadYAML(`
  910. version: "3.2"
  911. networks:
  912. mynet1:
  913. driver: overlay
  914. attachable: true
  915. mynet2:
  916. driver: bridge
  917. `)
  918. if !assert.NoError(t, err) {
  919. return
  920. }
  921. expected := map[string]types.NetworkConfig{
  922. "mynet1": {
  923. Driver: "overlay",
  924. Attachable: true,
  925. },
  926. "mynet2": {
  927. Driver: "bridge",
  928. Attachable: false,
  929. },
  930. }
  931. assert.Equal(t, expected, config.Networks)
  932. }
  933. func TestLoadExpandedPortFormat(t *testing.T) {
  934. config, err := loadYAML(`
  935. version: "3.2"
  936. services:
  937. web:
  938. image: busybox
  939. ports:
  940. - "80-82:8080-8082"
  941. - "90-92:8090-8092/udp"
  942. - "85:8500"
  943. - 8600
  944. - protocol: udp
  945. target: 53
  946. published: 10053
  947. - mode: host
  948. target: 22
  949. published: 10022
  950. `)
  951. if !assert.NoError(t, err) {
  952. return
  953. }
  954. expected := []types.ServicePortConfig{
  955. {
  956. Mode: "ingress",
  957. Target: 8080,
  958. Published: 80,
  959. Protocol: "tcp",
  960. },
  961. {
  962. Mode: "ingress",
  963. Target: 8081,
  964. Published: 81,
  965. Protocol: "tcp",
  966. },
  967. {
  968. Mode: "ingress",
  969. Target: 8082,
  970. Published: 82,
  971. Protocol: "tcp",
  972. },
  973. {
  974. Mode: "ingress",
  975. Target: 8090,
  976. Published: 90,
  977. Protocol: "udp",
  978. },
  979. {
  980. Mode: "ingress",
  981. Target: 8091,
  982. Published: 91,
  983. Protocol: "udp",
  984. },
  985. {
  986. Mode: "ingress",
  987. Target: 8092,
  988. Published: 92,
  989. Protocol: "udp",
  990. },
  991. {
  992. Mode: "ingress",
  993. Target: 8500,
  994. Published: 85,
  995. Protocol: "tcp",
  996. },
  997. {
  998. Mode: "ingress",
  999. Target: 8600,
  1000. Published: 0,
  1001. Protocol: "tcp",
  1002. },
  1003. {
  1004. Target: 53,
  1005. Published: 10053,
  1006. Protocol: "udp",
  1007. },
  1008. {
  1009. Mode: "host",
  1010. Target: 22,
  1011. Published: 10022,
  1012. },
  1013. }
  1014. assert.Equal(t, 1, len(config.Services))
  1015. assert.Equal(t, expected, config.Services[0].Ports)
  1016. }
  1017. func TestLoadExpandedMountFormat(t *testing.T) {
  1018. config, err := loadYAML(`
  1019. version: "3.2"
  1020. services:
  1021. web:
  1022. image: busybox
  1023. volumes:
  1024. - type: volume
  1025. source: foo
  1026. target: /target
  1027. read_only: true
  1028. volumes:
  1029. foo: {}
  1030. `)
  1031. if !assert.NoError(t, err) {
  1032. return
  1033. }
  1034. expected := types.ServiceVolumeConfig{
  1035. Type: "volume",
  1036. Source: "foo",
  1037. Target: "/target",
  1038. ReadOnly: true,
  1039. }
  1040. assert.Equal(t, 1, len(config.Services))
  1041. assert.Equal(t, 1, len(config.Services[0].Volumes))
  1042. assert.Equal(t, expected, config.Services[0].Volumes[0])
  1043. }