loader_test.go 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  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. 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 = types.Dict{
  65. "version": "3",
  66. "services": types.Dict{
  67. "foo": types.Dict{
  68. "image": "busybox",
  69. "networks": types.Dict{"with_me": nil},
  70. },
  71. "bar": types.Dict{
  72. "image": "busybox",
  73. "environment": []interface{}{"FOO=1"},
  74. "networks": []interface{}{"with_ipam"},
  75. },
  76. },
  77. "volumes": types.Dict{
  78. "hello": types.Dict{
  79. "driver": "default",
  80. "driver_opts": types.Dict{
  81. "beep": "boop",
  82. },
  83. },
  84. },
  85. "networks": types.Dict{
  86. "default": types.Dict{
  87. "driver": "bridge",
  88. "driver_opts": types.Dict{
  89. "beep": "boop",
  90. },
  91. },
  92. "with_ipam": types.Dict{
  93. "ipam": types.Dict{
  94. "driver": "default",
  95. "config": []interface{}{
  96. types.Dict{
  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. },
  607. Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"},
  608. DNS: []string{"8.8.8.8", "9.9.9.9"},
  609. DNSSearch: []string{"dc1.example.com", "dc2.example.com"},
  610. DomainName: "foo.com",
  611. Entrypoint: []string{"/code/entrypoint.sh", "-p", "3000"},
  612. Environment: map[string]*string{
  613. "FOO": strPtr("foo_from_env_file"),
  614. "BAR": strPtr("bar_from_env_file_2"),
  615. "BAZ": strPtr("baz_from_service_def"),
  616. "QUX": strPtr("qux_from_environment"),
  617. },
  618. EnvFile: []string{
  619. "./example1.env",
  620. "./example2.env",
  621. },
  622. Expose: []string{"3000", "8000"},
  623. ExternalLinks: []string{
  624. "redis_1",
  625. "project_db_1:mysql",
  626. "project_db_1:postgresql",
  627. },
  628. ExtraHosts: map[string]string{
  629. "otherhost": "50.31.209.229",
  630. "somehost": "162.242.195.82",
  631. },
  632. HealthCheck: &types.HealthCheckConfig{
  633. Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}),
  634. Interval: "10s",
  635. Timeout: "1s",
  636. Retries: uint64Ptr(5),
  637. },
  638. Hostname: "foo",
  639. Image: "redis",
  640. Ipc: "host",
  641. Labels: map[string]string{
  642. "com.example.description": "Accounting webapp",
  643. "com.example.number": "42",
  644. "com.example.empty-label": "",
  645. },
  646. Links: []string{
  647. "db",
  648. "db:database",
  649. "redis",
  650. },
  651. Logging: &types.LoggingConfig{
  652. Driver: "syslog",
  653. Options: map[string]string{
  654. "syslog-address": "tcp://192.168.0.42:123",
  655. },
  656. },
  657. MacAddress: "02:42:ac:11:65:43",
  658. NetworkMode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b",
  659. Networks: map[string]*types.ServiceNetworkConfig{
  660. "some-network": {
  661. Aliases: []string{"alias1", "alias3"},
  662. Ipv4Address: "",
  663. Ipv6Address: "",
  664. },
  665. "other-network": {
  666. Ipv4Address: "172.16.238.10",
  667. Ipv6Address: "2001:3984:3989::10",
  668. },
  669. "other-other-network": nil,
  670. },
  671. Pid: "host",
  672. Ports: []types.ServicePortConfig{
  673. //"3000",
  674. {
  675. Mode: "ingress",
  676. Target: 3000,
  677. Protocol: "tcp",
  678. },
  679. //"3000-3005",
  680. {
  681. Mode: "ingress",
  682. Target: 3000,
  683. Protocol: "tcp",
  684. },
  685. {
  686. Mode: "ingress",
  687. Target: 3001,
  688. Protocol: "tcp",
  689. },
  690. {
  691. Mode: "ingress",
  692. Target: 3002,
  693. Protocol: "tcp",
  694. },
  695. {
  696. Mode: "ingress",
  697. Target: 3003,
  698. Protocol: "tcp",
  699. },
  700. {
  701. Mode: "ingress",
  702. Target: 3004,
  703. Protocol: "tcp",
  704. },
  705. {
  706. Mode: "ingress",
  707. Target: 3005,
  708. Protocol: "tcp",
  709. },
  710. //"8000:8000",
  711. {
  712. Mode: "ingress",
  713. Target: 8000,
  714. Published: 8000,
  715. Protocol: "tcp",
  716. },
  717. //"9090-9091:8080-8081",
  718. {
  719. Mode: "ingress",
  720. Target: 8080,
  721. Published: 9090,
  722. Protocol: "tcp",
  723. },
  724. {
  725. Mode: "ingress",
  726. Target: 8081,
  727. Published: 9091,
  728. Protocol: "tcp",
  729. },
  730. //"49100:22",
  731. {
  732. Mode: "ingress",
  733. Target: 22,
  734. Published: 49100,
  735. Protocol: "tcp",
  736. },
  737. //"127.0.0.1:8001:8001",
  738. {
  739. Mode: "ingress",
  740. Target: 8001,
  741. Published: 8001,
  742. Protocol: "tcp",
  743. },
  744. //"127.0.0.1:5000-5010:5000-5010",
  745. {
  746. Mode: "ingress",
  747. Target: 5000,
  748. Published: 5000,
  749. Protocol: "tcp",
  750. },
  751. {
  752. Mode: "ingress",
  753. Target: 5001,
  754. Published: 5001,
  755. Protocol: "tcp",
  756. },
  757. {
  758. Mode: "ingress",
  759. Target: 5002,
  760. Published: 5002,
  761. Protocol: "tcp",
  762. },
  763. {
  764. Mode: "ingress",
  765. Target: 5003,
  766. Published: 5003,
  767. Protocol: "tcp",
  768. },
  769. {
  770. Mode: "ingress",
  771. Target: 5004,
  772. Published: 5004,
  773. Protocol: "tcp",
  774. },
  775. {
  776. Mode: "ingress",
  777. Target: 5005,
  778. Published: 5005,
  779. Protocol: "tcp",
  780. },
  781. {
  782. Mode: "ingress",
  783. Target: 5006,
  784. Published: 5006,
  785. Protocol: "tcp",
  786. },
  787. {
  788. Mode: "ingress",
  789. Target: 5007,
  790. Published: 5007,
  791. Protocol: "tcp",
  792. },
  793. {
  794. Mode: "ingress",
  795. Target: 5008,
  796. Published: 5008,
  797. Protocol: "tcp",
  798. },
  799. {
  800. Mode: "ingress",
  801. Target: 5009,
  802. Published: 5009,
  803. Protocol: "tcp",
  804. },
  805. {
  806. Mode: "ingress",
  807. Target: 5010,
  808. Published: 5010,
  809. Protocol: "tcp",
  810. },
  811. },
  812. Privileged: true,
  813. ReadOnly: true,
  814. Restart: "always",
  815. SecurityOpt: []string{
  816. "label=level:s0:c100,c200",
  817. "label=type:svirt_apache_t",
  818. },
  819. StdinOpen: true,
  820. StopSignal: "SIGUSR1",
  821. StopGracePeriod: &stopGracePeriod,
  822. Tmpfs: []string{"/run", "/tmp"},
  823. Tty: true,
  824. Ulimits: map[string]*types.UlimitsConfig{
  825. "nproc": {
  826. Single: 65535,
  827. },
  828. "nofile": {
  829. Soft: 20000,
  830. Hard: 40000,
  831. },
  832. },
  833. User: "someone",
  834. Volumes: []types.ServiceVolumeConfig{
  835. {Target: "/var/lib/mysql", Type: "volume"},
  836. {Source: "/opt/data", Target: "/var/lib/mysql", Type: "bind"},
  837. {Source: workingDir, Target: "/code", Type: "bind"},
  838. {Source: workingDir + "/static", Target: "/var/www/html", Type: "bind"},
  839. {Source: homeDir + "/configs", Target: "/etc/configs/", Type: "bind", ReadOnly: true},
  840. {Source: "datavolume", Target: "/var/lib/mysql", Type: "volume"},
  841. },
  842. WorkingDir: "/code",
  843. }
  844. assert.Equal(t, []types.ServiceConfig{expectedServiceConfig}, config.Services)
  845. expectedNetworkConfig := map[string]types.NetworkConfig{
  846. "some-network": {},
  847. "other-network": {
  848. Driver: "overlay",
  849. DriverOpts: map[string]string{
  850. "foo": "bar",
  851. "baz": "1",
  852. },
  853. Ipam: types.IPAMConfig{
  854. Driver: "overlay",
  855. Config: []*types.IPAMPool{
  856. {Subnet: "172.16.238.0/24"},
  857. {Subnet: "2001:3984:3989::/64"},
  858. },
  859. },
  860. },
  861. "external-network": {
  862. External: types.External{
  863. Name: "external-network",
  864. External: true,
  865. },
  866. },
  867. "other-external-network": {
  868. External: types.External{
  869. Name: "my-cool-network",
  870. External: true,
  871. },
  872. },
  873. }
  874. assert.Equal(t, expectedNetworkConfig, config.Networks)
  875. expectedVolumeConfig := map[string]types.VolumeConfig{
  876. "some-volume": {},
  877. "other-volume": {
  878. Driver: "flocker",
  879. DriverOpts: map[string]string{
  880. "foo": "bar",
  881. "baz": "1",
  882. },
  883. },
  884. "external-volume": {
  885. External: types.External{
  886. Name: "external-volume",
  887. External: true,
  888. },
  889. },
  890. "other-external-volume": {
  891. External: types.External{
  892. Name: "my-cool-volume",
  893. External: true,
  894. },
  895. },
  896. }
  897. assert.Equal(t, expectedVolumeConfig, config.Volumes)
  898. }
  899. func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
  900. sort.Sort(servicesByName(services))
  901. return services
  902. }
  903. type servicesByName []types.ServiceConfig
  904. func (sbn servicesByName) Len() int { return len(sbn) }
  905. func (sbn servicesByName) Swap(i, j int) { sbn[i], sbn[j] = sbn[j], sbn[i] }
  906. func (sbn servicesByName) Less(i, j int) bool { return sbn[i].Name < sbn[j].Name }
  907. func TestLoadAttachableNetwork(t *testing.T) {
  908. config, err := loadYAML(`
  909. version: "3.2"
  910. networks:
  911. mynet1:
  912. driver: overlay
  913. attachable: true
  914. mynet2:
  915. driver: bridge
  916. `)
  917. if !assert.NoError(t, err) {
  918. return
  919. }
  920. expected := map[string]types.NetworkConfig{
  921. "mynet1": {
  922. Driver: "overlay",
  923. Attachable: true,
  924. },
  925. "mynet2": {
  926. Driver: "bridge",
  927. Attachable: false,
  928. },
  929. }
  930. assert.Equal(t, expected, config.Networks)
  931. }
  932. func TestLoadExpandedPortFormat(t *testing.T) {
  933. config, err := loadYAML(`
  934. version: "3.2"
  935. services:
  936. web:
  937. image: busybox
  938. ports:
  939. - "80-82:8080-8082"
  940. - "90-92:8090-8092/udp"
  941. - "85:8500"
  942. - 8600
  943. - protocol: udp
  944. target: 53
  945. published: 10053
  946. - mode: host
  947. target: 22
  948. published: 10022
  949. `)
  950. if !assert.NoError(t, err) {
  951. return
  952. }
  953. expected := []types.ServicePortConfig{
  954. {
  955. Mode: "ingress",
  956. Target: 8080,
  957. Published: 80,
  958. Protocol: "tcp",
  959. },
  960. {
  961. Mode: "ingress",
  962. Target: 8081,
  963. Published: 81,
  964. Protocol: "tcp",
  965. },
  966. {
  967. Mode: "ingress",
  968. Target: 8082,
  969. Published: 82,
  970. Protocol: "tcp",
  971. },
  972. {
  973. Mode: "ingress",
  974. Target: 8090,
  975. Published: 90,
  976. Protocol: "udp",
  977. },
  978. {
  979. Mode: "ingress",
  980. Target: 8091,
  981. Published: 91,
  982. Protocol: "udp",
  983. },
  984. {
  985. Mode: "ingress",
  986. Target: 8092,
  987. Published: 92,
  988. Protocol: "udp",
  989. },
  990. {
  991. Mode: "ingress",
  992. Target: 8500,
  993. Published: 85,
  994. Protocol: "tcp",
  995. },
  996. {
  997. Mode: "ingress",
  998. Target: 8600,
  999. Published: 0,
  1000. Protocol: "tcp",
  1001. },
  1002. {
  1003. Target: 53,
  1004. Published: 10053,
  1005. Protocol: "udp",
  1006. },
  1007. {
  1008. Mode: "host",
  1009. Target: 22,
  1010. Published: 10022,
  1011. },
  1012. }
  1013. assert.Equal(t, 1, len(config.Services))
  1014. assert.Equal(t, expected, config.Services[0].Ports)
  1015. }
  1016. func TestLoadExpandedMountFormat(t *testing.T) {
  1017. config, err := loadYAML(`
  1018. version: "3.2"
  1019. services:
  1020. web:
  1021. image: busybox
  1022. volumes:
  1023. - type: volume
  1024. source: foo
  1025. target: /target
  1026. read_only: true
  1027. volumes:
  1028. foo: {}
  1029. `)
  1030. if !assert.NoError(t, err) {
  1031. return
  1032. }
  1033. expected := types.ServiceVolumeConfig{
  1034. Type: "volume",
  1035. Source: "foo",
  1036. Target: "/target",
  1037. ReadOnly: true,
  1038. }
  1039. assert.Equal(t, 1, len(config.Services))
  1040. assert.Equal(t, 1, len(config.Services[0].Volumes))
  1041. assert.Equal(t, expected, config.Services[0].Volumes[0])
  1042. }