loader_test.go 22 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  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, 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. 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, nil))
  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. `, nil)
  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, nil)
  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", nil)
  184. assert.Error(t, err)
  185. assert.Contains(t, err.Error(), "Top-level object must be a mapping")
  186. _, err = loadYAML("\"hello\"", nil)
  187. assert.Error(t, err)
  188. assert.Contains(t, err.Error(), "Top-level object must be a mapping")
  189. _, err = loadYAML("[\"hello\"]", nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  243. assert.NoError(t, err)
  244. _, err = loadYAML(`
  245. version: "3.0"
  246. services:
  247. foo:
  248. image: busybox
  249. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. `, nil)
  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. QUX:
  355. QUUX:
  356. list-env:
  357. image: busybox
  358. environment:
  359. - FOO=1
  360. - BAR=2
  361. - BAZ=2.5
  362. - QUX
  363. - QUUX=
  364. `, map[string]string{"QUX": "qux"})
  365. assert.NoError(t, err)
  366. expected := types.MappingWithEquals{
  367. "FOO": "1",
  368. "BAR": "2",
  369. "BAZ": "2.5",
  370. "QUX": "qux",
  371. "QUUX": "",
  372. }
  373. assert.Equal(t, 2, len(config.Services))
  374. for _, service := range config.Services {
  375. assert.Equal(t, expected, service.Environment)
  376. }
  377. }
  378. func TestInvalidEnvironmentValue(t *testing.T) {
  379. _, err := loadYAML(`
  380. version: "3"
  381. services:
  382. dict-env:
  383. image: busybox
  384. environment:
  385. FOO: ["1"]
  386. `, nil)
  387. assert.Error(t, err)
  388. assert.Contains(t, err.Error(), "services.dict-env.environment.FOO must be a string, number or null")
  389. }
  390. func TestInvalidEnvironmentObject(t *testing.T) {
  391. _, err := loadYAML(`
  392. version: "3"
  393. services:
  394. dict-env:
  395. image: busybox
  396. environment: "FOO=1"
  397. `, nil)
  398. assert.Error(t, err)
  399. assert.Contains(t, err.Error(), "services.dict-env.environment must be a mapping")
  400. }
  401. func TestEnvironmentInterpolation(t *testing.T) {
  402. home := "/home/foo"
  403. config, err := loadYAML(`
  404. version: "3"
  405. services:
  406. test:
  407. image: busybox
  408. labels:
  409. - home1=$HOME
  410. - home2=${HOME}
  411. - nonexistent=$NONEXISTENT
  412. - default=${NONEXISTENT-default}
  413. networks:
  414. test:
  415. driver: $HOME
  416. volumes:
  417. test:
  418. driver: $HOME
  419. `, map[string]string{
  420. "HOME": home,
  421. "FOO": "foo",
  422. })
  423. assert.NoError(t, err)
  424. expectedLabels := types.MappingWithEquals{
  425. "home1": home,
  426. "home2": home,
  427. "nonexistent": "",
  428. "default": "default",
  429. }
  430. assert.Equal(t, expectedLabels, config.Services[0].Labels)
  431. assert.Equal(t, home, config.Networks["test"].Driver)
  432. assert.Equal(t, home, config.Volumes["test"].Driver)
  433. }
  434. func TestUnsupportedProperties(t *testing.T) {
  435. dict, err := ParseYAML([]byte(`
  436. version: "3"
  437. services:
  438. web:
  439. image: web
  440. build: ./web
  441. links:
  442. - bar
  443. db:
  444. image: db
  445. build: ./db
  446. `))
  447. assert.NoError(t, err)
  448. configDetails := buildConfigDetails(dict, nil)
  449. _, err = Load(configDetails)
  450. assert.NoError(t, err)
  451. unsupported := GetUnsupportedProperties(configDetails)
  452. assert.Equal(t, []string{"build", "links"}, unsupported)
  453. }
  454. func TestDeprecatedProperties(t *testing.T) {
  455. dict, err := ParseYAML([]byte(`
  456. version: "3"
  457. services:
  458. web:
  459. image: web
  460. container_name: web
  461. db:
  462. image: db
  463. container_name: db
  464. expose: ["5434"]
  465. `))
  466. assert.NoError(t, err)
  467. configDetails := buildConfigDetails(dict, nil)
  468. _, err = Load(configDetails)
  469. assert.NoError(t, err)
  470. deprecated := GetDeprecatedProperties(configDetails)
  471. assert.Equal(t, 2, len(deprecated))
  472. assert.Contains(t, deprecated, "container_name")
  473. assert.Contains(t, deprecated, "expose")
  474. }
  475. func TestForbiddenProperties(t *testing.T) {
  476. _, err := loadYAML(`
  477. version: "3"
  478. services:
  479. foo:
  480. image: busybox
  481. volumes:
  482. - /data
  483. volume_driver: some-driver
  484. bar:
  485. extends:
  486. service: foo
  487. `, nil)
  488. assert.Error(t, err)
  489. assert.IsType(t, &ForbiddenPropertiesError{}, err)
  490. fmt.Println(err)
  491. forbidden := err.(*ForbiddenPropertiesError).Properties
  492. assert.Equal(t, 2, len(forbidden))
  493. assert.Contains(t, forbidden, "volume_driver")
  494. assert.Contains(t, forbidden, "extends")
  495. }
  496. func TestInvalidExternalAndDriverCombination(t *testing.T) {
  497. _, err := loadYAML(`
  498. version: "3"
  499. volumes:
  500. external_volume:
  501. external: true
  502. driver: foobar
  503. `)
  504. assert.Error(t, err)
  505. assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"driver\" specified for volume")
  506. assert.Contains(t, err.Error(), "external_volume")
  507. }
  508. func TestInvalidExternalAndDirverOptsCombination(t *testing.T) {
  509. _, err := loadYAML(`
  510. version: "3"
  511. volumes:
  512. external_volume:
  513. external: true
  514. driver_opts:
  515. beep: boop
  516. `)
  517. assert.Error(t, err)
  518. assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"driver_opts\" specified for volume")
  519. assert.Contains(t, err.Error(), "external_volume")
  520. }
  521. func TestInvalidExternalAndLabelsCombination(t *testing.T) {
  522. _, err := loadYAML(`
  523. version: "3"
  524. volumes:
  525. external_volume:
  526. external: true
  527. labels:
  528. - beep=boop
  529. `)
  530. assert.Error(t, err)
  531. assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"labels\" specified for volume")
  532. assert.Contains(t, err.Error(), "external_volume")
  533. }
  534. func durationPtr(value time.Duration) *time.Duration {
  535. return &value
  536. }
  537. func int64Ptr(value int64) *int64 {
  538. return &value
  539. }
  540. func uint64Ptr(value uint64) *uint64 {
  541. return &value
  542. }
  543. func TestFullExample(t *testing.T) {
  544. bytes, err := ioutil.ReadFile("full-example.yml")
  545. assert.NoError(t, err)
  546. homeDir := "/home/foo"
  547. config, err := loadYAML(string(bytes), map[string]string{"HOME": homeDir, "QUX": "2"})
  548. if !assert.NoError(t, err) {
  549. return
  550. }
  551. workingDir, err := os.Getwd()
  552. assert.NoError(t, err)
  553. stopGracePeriod := time.Duration(20 * time.Second)
  554. expectedServiceConfig := types.ServiceConfig{
  555. Name: "foo",
  556. CapAdd: []string{"ALL"},
  557. CapDrop: []string{"NET_ADMIN", "SYS_ADMIN"},
  558. CgroupParent: "m-executor-abcd",
  559. Command: []string{"bundle", "exec", "thin", "-p", "3000"},
  560. ContainerName: "my-web-container",
  561. DependsOn: []string{"db", "redis"},
  562. Deploy: types.DeployConfig{
  563. Mode: "replicated",
  564. Replicas: uint64Ptr(6),
  565. Labels: map[string]string{"FOO": "BAR"},
  566. UpdateConfig: &types.UpdateConfig{
  567. Parallelism: uint64Ptr(3),
  568. Delay: time.Duration(10 * time.Second),
  569. FailureAction: "continue",
  570. Monitor: time.Duration(60 * time.Second),
  571. MaxFailureRatio: 0.3,
  572. },
  573. Resources: types.Resources{
  574. Limits: &types.Resource{
  575. NanoCPUs: "0.001",
  576. MemoryBytes: 50 * 1024 * 1024,
  577. },
  578. Reservations: &types.Resource{
  579. NanoCPUs: "0.0001",
  580. MemoryBytes: 20 * 1024 * 1024,
  581. },
  582. },
  583. RestartPolicy: &types.RestartPolicy{
  584. Condition: "on_failure",
  585. Delay: durationPtr(5 * time.Second),
  586. MaxAttempts: uint64Ptr(3),
  587. Window: durationPtr(2 * time.Minute),
  588. },
  589. Placement: types.Placement{
  590. Constraints: []string{"node=foo"},
  591. },
  592. },
  593. Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"},
  594. DNS: []string{"8.8.8.8", "9.9.9.9"},
  595. DNSSearch: []string{"dc1.example.com", "dc2.example.com"},
  596. DomainName: "foo.com",
  597. Entrypoint: []string{"/code/entrypoint.sh", "-p", "3000"},
  598. Environment: map[string]string{
  599. "RACK_ENV": "development",
  600. "SHOW": "true",
  601. "SESSION_SECRET": "",
  602. "FOO": "1",
  603. "BAR": "2",
  604. "BAZ": "3",
  605. "QUX": "2",
  606. },
  607. EnvFile: []string{
  608. "./example1.env",
  609. "./example2.env",
  610. },
  611. Expose: []string{"3000", "8000"},
  612. ExternalLinks: []string{
  613. "redis_1",
  614. "project_db_1:mysql",
  615. "project_db_1:postgresql",
  616. },
  617. ExtraHosts: map[string]string{
  618. "otherhost": "50.31.209.229",
  619. "somehost": "162.242.195.82",
  620. },
  621. HealthCheck: &types.HealthCheckConfig{
  622. Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}),
  623. Interval: "10s",
  624. Timeout: "1s",
  625. Retries: uint64Ptr(5),
  626. },
  627. Hostname: "foo",
  628. Image: "redis",
  629. Ipc: "host",
  630. Labels: map[string]string{
  631. "com.example.description": "Accounting webapp",
  632. "com.example.number": "42",
  633. "com.example.empty-label": "",
  634. },
  635. Links: []string{
  636. "db",
  637. "db:database",
  638. "redis",
  639. },
  640. Logging: &types.LoggingConfig{
  641. Driver: "syslog",
  642. Options: map[string]string{
  643. "syslog-address": "tcp://192.168.0.42:123",
  644. },
  645. },
  646. MacAddress: "02:42:ac:11:65:43",
  647. NetworkMode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b",
  648. Networks: map[string]*types.ServiceNetworkConfig{
  649. "some-network": {
  650. Aliases: []string{"alias1", "alias3"},
  651. Ipv4Address: "",
  652. Ipv6Address: "",
  653. },
  654. "other-network": {
  655. Ipv4Address: "172.16.238.10",
  656. Ipv6Address: "2001:3984:3989::10",
  657. },
  658. "other-other-network": nil,
  659. },
  660. Pid: "host",
  661. Ports: []types.ServicePortConfig{
  662. //"3000",
  663. {
  664. Mode: "ingress",
  665. Target: 3000,
  666. Protocol: "tcp",
  667. },
  668. //"3000-3005",
  669. {
  670. Mode: "ingress",
  671. Target: 3000,
  672. Protocol: "tcp",
  673. },
  674. {
  675. Mode: "ingress",
  676. Target: 3001,
  677. Protocol: "tcp",
  678. },
  679. {
  680. Mode: "ingress",
  681. Target: 3002,
  682. Protocol: "tcp",
  683. },
  684. {
  685. Mode: "ingress",
  686. Target: 3003,
  687. Protocol: "tcp",
  688. },
  689. {
  690. Mode: "ingress",
  691. Target: 3004,
  692. Protocol: "tcp",
  693. },
  694. {
  695. Mode: "ingress",
  696. Target: 3005,
  697. Protocol: "tcp",
  698. },
  699. //"8000:8000",
  700. {
  701. Mode: "ingress",
  702. Target: 8000,
  703. Published: 8000,
  704. Protocol: "tcp",
  705. },
  706. //"9090-9091:8080-8081",
  707. {
  708. Mode: "ingress",
  709. Target: 8080,
  710. Published: 9090,
  711. Protocol: "tcp",
  712. },
  713. {
  714. Mode: "ingress",
  715. Target: 8081,
  716. Published: 9091,
  717. Protocol: "tcp",
  718. },
  719. //"49100:22",
  720. {
  721. Mode: "ingress",
  722. Target: 22,
  723. Published: 49100,
  724. Protocol: "tcp",
  725. },
  726. //"127.0.0.1:8001:8001",
  727. {
  728. Mode: "ingress",
  729. Target: 8001,
  730. Published: 8001,
  731. Protocol: "tcp",
  732. },
  733. //"127.0.0.1:5000-5010:5000-5010",
  734. {
  735. Mode: "ingress",
  736. Target: 5000,
  737. Published: 5000,
  738. Protocol: "tcp",
  739. },
  740. {
  741. Mode: "ingress",
  742. Target: 5001,
  743. Published: 5001,
  744. Protocol: "tcp",
  745. },
  746. {
  747. Mode: "ingress",
  748. Target: 5002,
  749. Published: 5002,
  750. Protocol: "tcp",
  751. },
  752. {
  753. Mode: "ingress",
  754. Target: 5003,
  755. Published: 5003,
  756. Protocol: "tcp",
  757. },
  758. {
  759. Mode: "ingress",
  760. Target: 5004,
  761. Published: 5004,
  762. Protocol: "tcp",
  763. },
  764. {
  765. Mode: "ingress",
  766. Target: 5005,
  767. Published: 5005,
  768. Protocol: "tcp",
  769. },
  770. {
  771. Mode: "ingress",
  772. Target: 5006,
  773. Published: 5006,
  774. Protocol: "tcp",
  775. },
  776. {
  777. Mode: "ingress",
  778. Target: 5007,
  779. Published: 5007,
  780. Protocol: "tcp",
  781. },
  782. {
  783. Mode: "ingress",
  784. Target: 5008,
  785. Published: 5008,
  786. Protocol: "tcp",
  787. },
  788. {
  789. Mode: "ingress",
  790. Target: 5009,
  791. Published: 5009,
  792. Protocol: "tcp",
  793. },
  794. {
  795. Mode: "ingress",
  796. Target: 5010,
  797. Published: 5010,
  798. Protocol: "tcp",
  799. },
  800. },
  801. Privileged: true,
  802. ReadOnly: true,
  803. Restart: "always",
  804. SecurityOpt: []string{
  805. "label=level:s0:c100,c200",
  806. "label=type:svirt_apache_t",
  807. },
  808. StdinOpen: true,
  809. StopSignal: "SIGUSR1",
  810. StopGracePeriod: &stopGracePeriod,
  811. Tmpfs: []string{"/run", "/tmp"},
  812. Tty: true,
  813. Ulimits: map[string]*types.UlimitsConfig{
  814. "nproc": {
  815. Single: 65535,
  816. },
  817. "nofile": {
  818. Soft: 20000,
  819. Hard: 40000,
  820. },
  821. },
  822. User: "someone",
  823. Volumes: []types.ServiceVolumeConfig{
  824. {Target: "/var/lib/mysql", Type: "volume"},
  825. {Source: "/opt/data", Target: "/var/lib/mysql", Type: "bind"},
  826. {Source: workingDir, Target: "/code", Type: "bind"},
  827. {Source: workingDir + "/static", Target: "/var/www/html", Type: "bind"},
  828. {Source: homeDir + "/configs", Target: "/etc/configs/", Type: "bind", ReadOnly: true},
  829. {Source: "datavolume", Target: "/var/lib/mysql", Type: "volume"},
  830. },
  831. WorkingDir: "/code",
  832. }
  833. assert.Equal(t, []types.ServiceConfig{expectedServiceConfig}, config.Services)
  834. expectedNetworkConfig := map[string]types.NetworkConfig{
  835. "some-network": {},
  836. "other-network": {
  837. Driver: "overlay",
  838. DriverOpts: map[string]string{
  839. "foo": "bar",
  840. "baz": "1",
  841. },
  842. Ipam: types.IPAMConfig{
  843. Driver: "overlay",
  844. Config: []*types.IPAMPool{
  845. {Subnet: "172.16.238.0/24"},
  846. {Subnet: "2001:3984:3989::/64"},
  847. },
  848. },
  849. },
  850. "external-network": {
  851. External: types.External{
  852. Name: "external-network",
  853. External: true,
  854. },
  855. },
  856. "other-external-network": {
  857. External: types.External{
  858. Name: "my-cool-network",
  859. External: true,
  860. },
  861. },
  862. }
  863. assert.Equal(t, expectedNetworkConfig, config.Networks)
  864. expectedVolumeConfig := map[string]types.VolumeConfig{
  865. "some-volume": {},
  866. "other-volume": {
  867. Driver: "flocker",
  868. DriverOpts: map[string]string{
  869. "foo": "bar",
  870. "baz": "1",
  871. },
  872. },
  873. "external-volume": {
  874. External: types.External{
  875. Name: "external-volume",
  876. External: true,
  877. },
  878. },
  879. "other-external-volume": {
  880. External: types.External{
  881. Name: "my-cool-volume",
  882. External: true,
  883. },
  884. },
  885. }
  886. assert.Equal(t, expectedVolumeConfig, config.Volumes)
  887. }
  888. func loadYAML(yaml string, env map[string]string) (*types.Config, error) {
  889. dict, err := ParseYAML([]byte(yaml))
  890. if err != nil {
  891. return nil, err
  892. }
  893. return Load(buildConfigDetails(dict, env))
  894. }
  895. func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
  896. sort.Sort(servicesByName(services))
  897. return services
  898. }
  899. type servicesByName []types.ServiceConfig
  900. func (sbn servicesByName) Len() int { return len(sbn) }
  901. func (sbn servicesByName) Swap(i, j int) { sbn[i], sbn[j] = sbn[j], sbn[i] }
  902. func (sbn servicesByName) Less(i, j int) bool { return sbn[i].Name < sbn[j].Name }
  903. func TestLoadAttachableNetwork(t *testing.T) {
  904. config, err := loadYAML(`
  905. version: "3.2"
  906. networks:
  907. mynet1:
  908. driver: overlay
  909. attachable: true
  910. mynet2:
  911. driver: bridge
  912. `)
  913. if !assert.NoError(t, err) {
  914. return
  915. }
  916. expected := map[string]types.NetworkConfig{
  917. "mynet1": {
  918. Driver: "overlay",
  919. Attachable: true,
  920. },
  921. "mynet2": {
  922. Driver: "bridge",
  923. Attachable: false,
  924. },
  925. }
  926. assert.Equal(t, expected, config.Networks)
  927. }
  928. func TestLoadExpandedPortFormat(t *testing.T) {
  929. config, err := loadYAML(`
  930. version: "3.2"
  931. services:
  932. web:
  933. image: busybox
  934. ports:
  935. - "80-82:8080-8082"
  936. - "90-92:8090-8092/udp"
  937. - "85:8500"
  938. - 8600
  939. - protocol: udp
  940. target: 53
  941. published: 10053
  942. - mode: host
  943. target: 22
  944. published: 10022
  945. `)
  946. if !assert.NoError(t, err) {
  947. return
  948. }
  949. expected := []types.ServicePortConfig{
  950. {
  951. Mode: "ingress",
  952. Target: 8080,
  953. Published: 80,
  954. Protocol: "tcp",
  955. },
  956. {
  957. Mode: "ingress",
  958. Target: 8081,
  959. Published: 81,
  960. Protocol: "tcp",
  961. },
  962. {
  963. Mode: "ingress",
  964. Target: 8082,
  965. Published: 82,
  966. Protocol: "tcp",
  967. },
  968. {
  969. Mode: "ingress",
  970. Target: 8090,
  971. Published: 90,
  972. Protocol: "udp",
  973. },
  974. {
  975. Mode: "ingress",
  976. Target: 8091,
  977. Published: 91,
  978. Protocol: "udp",
  979. },
  980. {
  981. Mode: "ingress",
  982. Target: 8092,
  983. Published: 92,
  984. Protocol: "udp",
  985. },
  986. {
  987. Mode: "ingress",
  988. Target: 8500,
  989. Published: 85,
  990. Protocol: "tcp",
  991. },
  992. {
  993. Mode: "ingress",
  994. Target: 8600,
  995. Published: 0,
  996. Protocol: "tcp",
  997. },
  998. {
  999. Target: 53,
  1000. Published: 10053,
  1001. Protocol: "udp",
  1002. },
  1003. {
  1004. Mode: "host",
  1005. Target: 22,
  1006. Published: 10022,
  1007. },
  1008. }
  1009. assert.Equal(t, 1, len(config.Services))
  1010. assert.Equal(t, expected, config.Services[0].Ports)
  1011. }
  1012. func TestLoadExpandedMountFormat(t *testing.T) {
  1013. config, err := loadYAML(`
  1014. version: "3.2"
  1015. services:
  1016. web:
  1017. image: busybox
  1018. volumes:
  1019. - type: volume
  1020. source: foo
  1021. target: /target
  1022. read_only: true
  1023. volumes:
  1024. foo: {}
  1025. `)
  1026. if !assert.NoError(t, err) {
  1027. return
  1028. }
  1029. expected := types.ServiceVolumeConfig{
  1030. Type: "volume",
  1031. Source: "foo",
  1032. Target: "/target",
  1033. ReadOnly: true,
  1034. }
  1035. assert.Equal(t, 1, len(config.Services))
  1036. assert.Equal(t, 1, len(config.Services[0].Volumes))
  1037. assert.Equal(t, expected, config.Services[0].Volumes[0])
  1038. }