loader_test.go 22 KB

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