docker_container.py 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036
  1. #!/usr/bin/python
  2. # pylint: skip-file
  3. # flake8: noqa
  4. # TODO: remove this file once openshift-ansible requires ansible >= 2.3.
  5. # This file is a copy of
  6. # https://github.com/ansible/ansible/blob/20bf02f/lib/ansible/modules/cloud/docker/docker_container.py.
  7. # It has been temporarily vendored here due to issue https://github.com/ansible/ansible/issues/22323.
  8. # Copyright 2016 Red Hat | Ansible
  9. #
  10. # This file is part of Ansible
  11. #
  12. # Ansible is free software: you can redistribute it and/or modify
  13. # it under the terms of the GNU General Public License as published by
  14. # the Free Software Foundation, either version 3 of the License, or
  15. # (at your option) any later version.
  16. #
  17. # Ansible is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU General Public License
  23. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  24. ANSIBLE_METADATA = {'status': ['preview'],
  25. 'supported_by': 'committer',
  26. 'version': '1.0'}
  27. DOCUMENTATION = '''
  28. ---
  29. module: docker_container
  30. short_description: manage docker containers
  31. description:
  32. - Manage the life cycle of docker containers.
  33. - Supports check mode. Run with --check and --diff to view config difference and list of actions to be taken.
  34. version_added: "2.1"
  35. options:
  36. blkio_weight:
  37. description:
  38. - Block IO (relative weight), between 10 and 1000.
  39. default: null
  40. required: false
  41. capabilities:
  42. description:
  43. - List of capabilities to add to the container.
  44. default: null
  45. required: false
  46. cleanup:
  47. description:
  48. - Use with I(detach) to remove the container after successful execution.
  49. default: false
  50. required: false
  51. version_added: "2.2"
  52. command:
  53. description:
  54. - Command to execute when the container starts.
  55. default: null
  56. required: false
  57. cpu_period:
  58. description:
  59. - Limit CPU CFS (Completely Fair Scheduler) period
  60. default: 0
  61. required: false
  62. cpu_quota:
  63. description:
  64. - Limit CPU CFS (Completely Fair Scheduler) quota
  65. default: 0
  66. required: false
  67. cpuset_cpus:
  68. description:
  69. - CPUs in which to allow execution C(1,3) or C(1-3).
  70. default: null
  71. required: false
  72. cpuset_mems:
  73. description:
  74. - Memory nodes (MEMs) in which to allow execution C(0-3) or C(0,1)
  75. default: null
  76. required: false
  77. cpu_shares:
  78. description:
  79. - CPU shares (relative weight).
  80. default: null
  81. required: false
  82. detach:
  83. description:
  84. - Enable detached mode to leave the container running in background.
  85. If disabled, the task will reflect the status of the container run (failed if the command failed).
  86. default: true
  87. required: false
  88. devices:
  89. description:
  90. - "List of host device bindings to add to the container. Each binding is a mapping expressed
  91. in the format: <path_on_host>:<path_in_container>:<cgroup_permissions>"
  92. default: null
  93. required: false
  94. dns_servers:
  95. description:
  96. - List of custom DNS servers.
  97. default: null
  98. required: false
  99. dns_search_domains:
  100. description:
  101. - List of custom DNS search domains.
  102. default: null
  103. required: false
  104. env:
  105. description:
  106. - Dictionary of key,value pairs.
  107. default: null
  108. required: false
  109. env_file:
  110. version_added: "2.2"
  111. description:
  112. - Path to a file containing environment variables I(FOO=BAR).
  113. - If variable also present in C(env), then C(env) value will override.
  114. - Requires docker-py >= 1.4.0.
  115. default: null
  116. required: false
  117. entrypoint:
  118. description:
  119. - Command that overwrites the default ENTRYPOINT of the image.
  120. default: null
  121. required: false
  122. etc_hosts:
  123. description:
  124. - Dict of host-to-IP mappings, where each host name is a key in the dictionary.
  125. Each host name will be added to the container's /etc/hosts file.
  126. default: null
  127. required: false
  128. exposed_ports:
  129. description:
  130. - List of additional container ports which informs Docker that the container
  131. listens on the specified network ports at runtime.
  132. If the port is already exposed using EXPOSE in a Dockerfile, it does not
  133. need to be exposed again.
  134. default: null
  135. required: false
  136. aliases:
  137. - exposed
  138. force_kill:
  139. description:
  140. - Use the kill command when stopping a running container.
  141. default: false
  142. required: false
  143. groups:
  144. description:
  145. - List of additional group names and/or IDs that the container process will run as.
  146. default: null
  147. required: false
  148. hostname:
  149. description:
  150. - Container hostname.
  151. default: null
  152. required: false
  153. ignore_image:
  154. description:
  155. - When C(state) is I(present) or I(started) the module compares the configuration of an existing
  156. container to requested configuration. The evaluation includes the image version. If
  157. the image version in the registry does not match the container, the container will be
  158. recreated. Stop this behavior by setting C(ignore_image) to I(True).
  159. default: false
  160. required: false
  161. version_added: "2.2"
  162. image:
  163. description:
  164. - Repository path and tag used to create the container. If an image is not found or pull is true, the image
  165. will be pulled from the registry. If no tag is included, 'latest' will be used.
  166. default: null
  167. required: false
  168. interactive:
  169. description:
  170. - Keep stdin open after a container is launched, even if not attached.
  171. default: false
  172. required: false
  173. ipc_mode:
  174. description:
  175. - Set the IPC mode for the container. Can be one of 'container:<name|id>' to reuse another
  176. container's IPC namespace or 'host' to use the host's IPC namespace within the container.
  177. default: null
  178. required: false
  179. keep_volumes:
  180. description:
  181. - Retain volumes associated with a removed container.
  182. default: true
  183. required: false
  184. kill_signal:
  185. description:
  186. - Override default signal used to kill a running container.
  187. default null:
  188. required: false
  189. kernel_memory:
  190. description:
  191. - "Kernel memory limit (format: <number>[<unit>]). Number is a positive integer.
  192. Unit can be one of b, k, m, or g. Minimum is 4M."
  193. default: 0
  194. required: false
  195. labels:
  196. description:
  197. - Dictionary of key value pairs.
  198. default: null
  199. required: false
  200. links:
  201. description:
  202. - List of name aliases for linked containers in the format C(container_name:alias)
  203. default: null
  204. required: false
  205. log_driver:
  206. description:
  207. - Specify the logging driver. Docker uses json-file by default.
  208. choices:
  209. - none
  210. - json-file
  211. - syslog
  212. - journald
  213. - gelf
  214. - fluentd
  215. - awslogs
  216. - splunk
  217. default: null
  218. required: false
  219. log_options:
  220. description:
  221. - Dictionary of options specific to the chosen log_driver. See https://docs.docker.com/engine/admin/logging/overview/
  222. for details.
  223. required: false
  224. default: null
  225. mac_address:
  226. description:
  227. - Container MAC address (e.g. 92:d0:c6:0a:29:33)
  228. default: null
  229. required: false
  230. memory:
  231. description:
  232. - "Memory limit (format: <number>[<unit>]). Number is a positive integer.
  233. Unit can be one of b, k, m, or g"
  234. default: 0
  235. required: false
  236. memory_reservation:
  237. description:
  238. - "Memory soft limit (format: <number>[<unit>]). Number is a positive integer.
  239. Unit can be one of b, k, m, or g"
  240. default: 0
  241. required: false
  242. memory_swap:
  243. description:
  244. - Total memory limit (memory + swap, format:<number>[<unit>]).
  245. Number is a positive integer. Unit can be one of b, k, m, or g.
  246. default: 0
  247. required: false
  248. memory_swappiness:
  249. description:
  250. - Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
  251. default: 0
  252. required: false
  253. name:
  254. description:
  255. - Assign a name to a new container or match an existing container.
  256. - When identifying an existing container name may be a name or a long or short container ID.
  257. required: true
  258. network_mode:
  259. description:
  260. - Connect the container to a network.
  261. choices:
  262. - bridge
  263. - container:<name|id>
  264. - host
  265. - none
  266. default: null
  267. required: false
  268. networks:
  269. description:
  270. - List of networks the container belongs to.
  271. - Each network is a dict with keys C(name), C(ipv4_address), C(ipv6_address), C(links), C(aliases).
  272. - For each network C(name) is required, all other keys are optional.
  273. - If included, C(links) or C(aliases) are lists.
  274. - For examples of the data structure and usage see EXAMPLES below.
  275. - To remove a container from one or more networks, use the C(purge_networks) option.
  276. default: null
  277. required: false
  278. version_added: "2.2"
  279. oom_killer:
  280. description:
  281. - Whether or not to disable OOM Killer for the container.
  282. default: false
  283. required: false
  284. oom_score_adj:
  285. description:
  286. - An integer value containing the score given to the container in order to tune OOM killer preferences.
  287. default: 0
  288. required: false
  289. version_added: "2.2"
  290. paused:
  291. description:
  292. - Use with the started state to pause running processes inside the container.
  293. default: false
  294. required: false
  295. pid_mode:
  296. description:
  297. - Set the PID namespace mode for the container. Currently only supports 'host'.
  298. default: null
  299. required: false
  300. privileged:
  301. description:
  302. - Give extended privileges to the container.
  303. default: false
  304. required: false
  305. published_ports:
  306. description:
  307. - List of ports to publish from the container to the host.
  308. - "Use docker CLI syntax: C(8000), C(9000:8000), or C(0.0.0.0:9000:8000), where 8000 is a
  309. container port, 9000 is a host port, and 0.0.0.0 is a host interface."
  310. - Container ports must be exposed either in the Dockerfile or via the C(expose) option.
  311. - A value of all will publish all exposed container ports to random host ports, ignoring
  312. any other mappings.
  313. - If C(networks) parameter is provided, will inspect each network to see if there exists
  314. a bridge network with optional parameter com.docker.network.bridge.host_binding_ipv4.
  315. If such a network is found, then published ports where no host IP address is specified
  316. will be bound to the host IP pointed to by com.docker.network.bridge.host_binding_ipv4.
  317. Note that the first bridge network with a com.docker.network.bridge.host_binding_ipv4
  318. value encountered in the list of C(networks) is the one that will be used.
  319. aliases:
  320. - ports
  321. required: false
  322. default: null
  323. pull:
  324. description:
  325. - If true, always pull the latest version of an image. Otherwise, will only pull an image when missing.
  326. default: false
  327. required: false
  328. purge_networks:
  329. description:
  330. - Remove the container from ALL networks not included in C(networks) parameter.
  331. - Any default networks such as I(bridge), if not found in C(networks), will be removed as well.
  332. default: false
  333. required: false
  334. version_added: "2.2"
  335. read_only:
  336. description:
  337. - Mount the container's root file system as read-only.
  338. default: false
  339. required: false
  340. recreate:
  341. description:
  342. - Use with present and started states to force the re-creation of an existing container.
  343. default: false
  344. required: false
  345. restart:
  346. description:
  347. - Use with started state to force a matching container to be stopped and restarted.
  348. default: false
  349. required: false
  350. restart_policy:
  351. description:
  352. - Container restart policy. Place quotes around I(no) option.
  353. choices:
  354. - always
  355. - no
  356. - on-failure
  357. - unless-stopped
  358. default: on-failure
  359. required: false
  360. restart_retries:
  361. description:
  362. - Use with restart policy to control maximum number of restart attempts.
  363. default: 0
  364. required: false
  365. shm_size:
  366. description:
  367. - Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.
  368. Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes).
  369. - Omitting the unit defaults to bytes. If you omit the size entirely, the system uses `64m`.
  370. default: null
  371. required: false
  372. security_opts:
  373. description:
  374. - List of security options in the form of C("label:user:User")
  375. default: null
  376. required: false
  377. state:
  378. description:
  379. - 'I(absent) - A container matching the specified name will be stopped and removed. Use force_kill to kill the container
  380. rather than stopping it. Use keep_volumes to retain volumes associated with the removed container.'
  381. - 'I(present) - Asserts the existence of a container matching the name and any provided configuration parameters. If no
  382. container matches the name, a container will be created. If a container matches the name but the provided configuration
  383. does not match, the container will be updated, if it can be. If it cannot be updated, it will be removed and re-created
  384. with the requested config. Image version will be taken into account when comparing configuration. To ignore image
  385. version use the ignore_image option. Use the recreate option to force the re-creation of the matching container. Use
  386. force_kill to kill the container rather than stopping it. Use keep_volumes to retain volumes associated with a removed
  387. container.'
  388. - 'I(started) - Asserts there is a running container matching the name and any provided configuration. If no container
  389. matches the name, a container will be created and started. If a container matching the name is found but the
  390. configuration does not match, the container will be updated, if it can be. If it cannot be updated, it will be removed
  391. and a new container will be created with the requested configuration and started. Image version will be taken into
  392. account when comparing configuration. To ignore image version use the ignore_image option. Use recreate to always
  393. re-create a matching container, even if it is running. Use restart to force a matching container to be stopped and
  394. restarted. Use force_kill to kill a container rather than stopping it. Use keep_volumes to retain volumes associated
  395. with a removed container.'
  396. - 'I(stopped) - Asserts that the container is first I(present), and then if the container is running moves it to a stopped
  397. state. Use force_kill to kill a container rather than stopping it.'
  398. required: false
  399. default: started
  400. choices:
  401. - absent
  402. - present
  403. - stopped
  404. - started
  405. stop_signal:
  406. description:
  407. - Override default signal used to stop the container.
  408. default: null
  409. required: false
  410. stop_timeout:
  411. description:
  412. - Number of seconds to wait for the container to stop before sending SIGKILL.
  413. required: false
  414. default: null
  415. trust_image_content:
  416. description:
  417. - If true, skip image verification.
  418. default: false
  419. required: false
  420. tty:
  421. description:
  422. - Allocate a psuedo-TTY.
  423. default: false
  424. required: false
  425. ulimits:
  426. description:
  427. - "List of ulimit options. A ulimit is specified as C(nofile:262144:262144)"
  428. default: null
  429. required: false
  430. user:
  431. description:
  432. - Sets the username or UID used and optionally the groupname or GID for the specified command.
  433. - "Can be [ user | user:group | uid | uid:gid | user:gid | uid:group ]"
  434. default: null
  435. required: false
  436. uts:
  437. description:
  438. - Set the UTS namespace mode for the container.
  439. default: null
  440. required: false
  441. volumes:
  442. description:
  443. - List of volumes to mount within the container.
  444. - "Use docker CLI-style syntax: C(/host:/container[:mode])"
  445. - You can specify a read mode for the mount with either C(ro) or C(rw).
  446. - SELinux hosts can additionally use C(z) or C(Z) to use a shared or
  447. private label for the volume.
  448. default: null
  449. required: false
  450. volume_driver:
  451. description:
  452. - The container volume driver.
  453. default: none
  454. required: false
  455. volumes_from:
  456. description:
  457. - List of container names or Ids to get volumes from.
  458. default: null
  459. required: false
  460. extends_documentation_fragment:
  461. - docker
  462. author:
  463. - "Cove Schneider (@cove)"
  464. - "Joshua Conner (@joshuaconner)"
  465. - "Pavel Antonov (@softzilla)"
  466. - "Thomas Steinbach (@ThomasSteinbach)"
  467. - "Philippe Jandot (@zfil)"
  468. - "Daan Oosterveld (@dusdanig)"
  469. - "James Tanner (@jctanner)"
  470. - "Chris Houseknecht (@chouseknecht)"
  471. requirements:
  472. - "python >= 2.6"
  473. - "docker-py >= 1.7.0"
  474. - "Docker API >= 1.20"
  475. '''
  476. EXAMPLES = '''
  477. - name: Create a data container
  478. docker_container:
  479. name: mydata
  480. image: busybox
  481. volumes:
  482. - /data
  483. - name: Re-create a redis container
  484. docker_container:
  485. name: myredis
  486. image: redis
  487. command: redis-server --appendonly yes
  488. state: present
  489. recreate: yes
  490. exposed_ports:
  491. - 6379
  492. volumes_from:
  493. - mydata
  494. - name: Restart a container
  495. docker_container:
  496. name: myapplication
  497. image: someuser/appimage
  498. state: started
  499. restart: yes
  500. links:
  501. - "myredis:aliasedredis"
  502. devices:
  503. - "/dev/sda:/dev/xvda:rwm"
  504. ports:
  505. - "8080:9000"
  506. - "127.0.0.1:8081:9001/udp"
  507. env:
  508. SECRET_KEY: ssssh
  509. - name: Container present
  510. docker_container:
  511. name: mycontainer
  512. state: present
  513. image: ubuntu:14.04
  514. command: sleep infinity
  515. - name: Stop a container
  516. docker_container:
  517. name: mycontainer
  518. state: stopped
  519. - name: Start 4 load-balanced containers
  520. docker_container:
  521. name: "container{{ item }}"
  522. recreate: yes
  523. image: someuser/anotherappimage
  524. command: sleep 1d
  525. with_sequence: count=4
  526. - name: remove container
  527. docker_container:
  528. name: ohno
  529. state: absent
  530. - name: Syslogging output
  531. docker_container:
  532. name: myservice
  533. image: busybox
  534. log_driver: syslog
  535. log_options:
  536. syslog-address: tcp://my-syslog-server:514
  537. syslog-facility: daemon
  538. # NOTE: in Docker 1.13+ the "syslog-tag" option was renamed to "tag" for
  539. # older docker installs, use "syslog-tag" instead
  540. tag: myservice
  541. - name: Create db container and connect to network
  542. docker_container:
  543. name: db_test
  544. image: "postgres:latest"
  545. networks:
  546. - name: "{{ docker_network_name }}"
  547. - name: Start container, connect to network and link
  548. docker_container:
  549. name: sleeper
  550. image: ubuntu:14.04
  551. networks:
  552. - name: TestingNet
  553. ipv4_address: "172.1.1.100"
  554. aliases:
  555. - sleepyzz
  556. links:
  557. - db_test:db
  558. - name: TestingNet2
  559. - name: Start a container with a command
  560. docker_container:
  561. name: sleepy
  562. image: ubuntu:14.04
  563. command: sleep infinity
  564. - name: Add container to networks
  565. docker_container:
  566. name: sleepy
  567. networks:
  568. - name: TestingNet
  569. ipv4_address: 172.1.1.18
  570. links:
  571. - sleeper
  572. - name: TestingNet2
  573. ipv4_address: 172.1.10.20
  574. - name: Update network with aliases
  575. docker_container:
  576. name: sleepy
  577. networks:
  578. - name: TestingNet
  579. aliases:
  580. - sleepyz
  581. - zzzz
  582. - name: Remove container from one network
  583. docker_container:
  584. name: sleepy
  585. networks:
  586. - name: TestingNet2
  587. purge_networks: yes
  588. - name: Remove container from all networks
  589. docker_container:
  590. name: sleepy
  591. purge_networks: yes
  592. '''
  593. RETURN = '''
  594. docker_container:
  595. description:
  596. - Before 2.3 this was 'ansible_docker_container' but was renamed due to conflicts with the connection plugin.
  597. - Facts representing the current state of the container. Matches the docker inspection output.
  598. - Note that facts are not part of registered vars but accessible directly.
  599. - Empty if C(state) is I(absent)
  600. - If detached is I(False), will include Output attribute containing any output from container run.
  601. returned: always
  602. type: dict
  603. sample: '{
  604. "AppArmorProfile": "",
  605. "Args": [],
  606. "Config": {
  607. "AttachStderr": false,
  608. "AttachStdin": false,
  609. "AttachStdout": false,
  610. "Cmd": [
  611. "/usr/bin/supervisord"
  612. ],
  613. "Domainname": "",
  614. "Entrypoint": null,
  615. "Env": [
  616. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  617. ],
  618. "ExposedPorts": {
  619. "443/tcp": {},
  620. "80/tcp": {}
  621. },
  622. "Hostname": "8e47bf643eb9",
  623. "Image": "lnmp_nginx:v1",
  624. "Labels": {},
  625. "OnBuild": null,
  626. "OpenStdin": false,
  627. "StdinOnce": false,
  628. "Tty": false,
  629. "User": "",
  630. "Volumes": {
  631. "/tmp/lnmp/nginx-sites/logs/": {}
  632. },
  633. ...
  634. }'
  635. '''
  636. import re
  637. from ansible.module_utils.docker_common import *
  638. try:
  639. from docker import utils
  640. if HAS_DOCKER_PY_2:
  641. from docker.types import Ulimit
  642. else:
  643. from docker.utils.types import Ulimit
  644. except:
  645. # missing docker-py handled in ansible.module_utils.docker
  646. pass
  647. REQUIRES_CONVERSION_TO_BYTES = [
  648. 'memory',
  649. 'memory_reservation',
  650. 'memory_swap',
  651. 'shm_size'
  652. ]
  653. VOLUME_PERMISSIONS = ('rw', 'ro', 'z', 'Z')
  654. class TaskParameters(DockerBaseClass):
  655. '''
  656. Access and parse module parameters
  657. '''
  658. def __init__(self, client):
  659. super(TaskParameters, self).__init__()
  660. self.client = client
  661. self.blkio_weight = None
  662. self.capabilities = None
  663. self.cleanup = None
  664. self.command = None
  665. self.cpu_period = None
  666. self.cpu_quota = None
  667. self.cpuset_cpus = None
  668. self.cpuset_mems = None
  669. self.cpu_shares = None
  670. self.detach = None
  671. self.debug = None
  672. self.devices = None
  673. self.dns_servers = None
  674. self.dns_opts = None
  675. self.dns_search_domains = None
  676. self.env = None
  677. self.env_file = None
  678. self.entrypoint = None
  679. self.etc_hosts = None
  680. self.exposed_ports = None
  681. self.force_kill = None
  682. self.groups = None
  683. self.hostname = None
  684. self.ignore_image = None
  685. self.image = None
  686. self.interactive = None
  687. self.ipc_mode = None
  688. self.keep_volumes = None
  689. self.kernel_memory = None
  690. self.kill_signal = None
  691. self.labels = None
  692. self.links = None
  693. self.log_driver = None
  694. self.log_options = None
  695. self.mac_address = None
  696. self.memory = None
  697. self.memory_reservation = None
  698. self.memory_swap = None
  699. self.memory_swappiness = None
  700. self.name = None
  701. self.network_mode = None
  702. self.networks = None
  703. self.oom_killer = None
  704. self.oom_score_adj = None
  705. self.paused = None
  706. self.pid_mode = None
  707. self.privileged = None
  708. self.purge_networks = None
  709. self.pull = None
  710. self.read_only = None
  711. self.recreate = None
  712. self.restart = None
  713. self.restart_retries = None
  714. self.restart_policy = None
  715. self.shm_size = None
  716. self.security_opts = None
  717. self.state = None
  718. self.stop_signal = None
  719. self.stop_timeout = None
  720. self.trust_image_content = None
  721. self.tty = None
  722. self.user = None
  723. self.uts = None
  724. self.volumes = None
  725. self.volume_binds = dict()
  726. self.volumes_from = None
  727. self.volume_driver = None
  728. for key, value in client.module.params.items():
  729. setattr(self, key, value)
  730. for param_name in REQUIRES_CONVERSION_TO_BYTES:
  731. if client.module.params.get(param_name):
  732. try:
  733. setattr(self, param_name, human_to_bytes(client.module.params.get(param_name)))
  734. except ValueError as exc:
  735. self.fail("Failed to convert %s to bytes: %s" % (param_name, exc))
  736. self.publish_all_ports = False
  737. self.published_ports = self._parse_publish_ports()
  738. if self.published_ports in ('all', 'ALL'):
  739. self.publish_all_ports = True
  740. self.published_ports = None
  741. self.ports = self._parse_exposed_ports(self.published_ports)
  742. self.log("expose ports:")
  743. self.log(self.ports, pretty_print=True)
  744. self.links = self._parse_links(self.links)
  745. if self.volumes:
  746. self.volumes = self._expand_host_paths()
  747. self.env = self._get_environment()
  748. self.ulimits = self._parse_ulimits()
  749. self.log_config = self._parse_log_config()
  750. self.exp_links = None
  751. self.volume_binds = self._get_volume_binds(self.volumes)
  752. self.log("volumes:")
  753. self.log(self.volumes, pretty_print=True)
  754. self.log("volume binds:")
  755. self.log(self.volume_binds, pretty_print=True)
  756. if self.networks:
  757. for network in self.networks:
  758. if not network.get('name'):
  759. self.fail("Parameter error: network must have a name attribute.")
  760. network['id'] = self._get_network_id(network['name'])
  761. if not network['id']:
  762. self.fail("Parameter error: network named %s could not be found. Does it exist?" % network['name'])
  763. if network.get('links'):
  764. network['links'] = self._parse_links(network['links'])
  765. def fail(self, msg):
  766. self.client.module.fail_json(msg=msg)
  767. @property
  768. def update_parameters(self):
  769. '''
  770. Returns parameters used to update a container
  771. '''
  772. update_parameters = dict(
  773. blkio_weight='blkio_weight',
  774. cpu_period='cpu_period',
  775. cpu_quota='cpu_quota',
  776. cpu_shares='cpu_shares',
  777. cpuset_cpus='cpuset_cpus',
  778. mem_limit='memory',
  779. mem_reservation='mem_reservation',
  780. memswap_limit='memory_swap',
  781. kernel_memory='kernel_memory'
  782. )
  783. result = dict()
  784. for key, value in update_parameters.items():
  785. if getattr(self, value, None) is not None:
  786. result[key] = getattr(self, value)
  787. return result
  788. @property
  789. def create_parameters(self):
  790. '''
  791. Returns parameters used to create a container
  792. '''
  793. create_params = dict(
  794. command='command',
  795. hostname='hostname',
  796. user='user',
  797. detach='detach',
  798. stdin_open='interactive',
  799. tty='tty',
  800. ports='ports',
  801. environment='env',
  802. name='name',
  803. entrypoint='entrypoint',
  804. cpu_shares='cpu_shares',
  805. mac_address='mac_address',
  806. labels='labels',
  807. stop_signal='stop_signal',
  808. volume_driver='volume_driver',
  809. )
  810. result = dict(
  811. host_config=self._host_config(),
  812. volumes=self._get_mounts(),
  813. )
  814. for key, value in create_params.items():
  815. if getattr(self, value, None) is not None:
  816. result[key] = getattr(self, value)
  817. return result
  818. def _expand_host_paths(self):
  819. new_vols = []
  820. for vol in self.volumes:
  821. if ':' in vol:
  822. if len(vol.split(':')) == 3:
  823. host, container, mode = vol.split(':')
  824. if re.match(r'[\.~]', host):
  825. host = os.path.abspath(host)
  826. new_vols.append("%s:%s:%s" % (host, container, mode))
  827. continue
  828. elif len(vol.split(':')) == 2:
  829. parts = vol.split(':')
  830. if parts[1] not in VOLUME_PERMISSIONS and re.match(r'[\.~]', parts[0]):
  831. host = os.path.abspath(parts[0])
  832. new_vols.append("%s:%s:rw" % (host, parts[1]))
  833. continue
  834. new_vols.append(vol)
  835. return new_vols
  836. def _get_mounts(self):
  837. '''
  838. Return a list of container mounts.
  839. :return:
  840. '''
  841. result = []
  842. if self.volumes:
  843. for vol in self.volumes:
  844. if ':' in vol:
  845. if len(vol.split(':')) == 3:
  846. host, container, _ = vol.split(':')
  847. result.append(container)
  848. continue
  849. if len(vol.split(':')) == 2:
  850. parts = vol.split(':')
  851. if parts[1] not in VOLUME_PERMISSIONS:
  852. result.append(parts[1])
  853. continue
  854. result.append(vol)
  855. self.log("mounts:")
  856. self.log(result, pretty_print=True)
  857. return result
  858. def _host_config(self):
  859. '''
  860. Returns parameters used to create a HostConfig object
  861. '''
  862. host_config_params=dict(
  863. port_bindings='published_ports',
  864. publish_all_ports='publish_all_ports',
  865. links='links',
  866. privileged='privileged',
  867. dns='dns_servers',
  868. dns_search='dns_search_domains',
  869. binds='volume_binds',
  870. volumes_from='volumes_from',
  871. network_mode='network_mode',
  872. cap_add='capabilities',
  873. extra_hosts='etc_hosts',
  874. read_only='read_only',
  875. ipc_mode='ipc_mode',
  876. security_opt='security_opts',
  877. ulimits='ulimits',
  878. log_config='log_config',
  879. mem_limit='memory',
  880. memswap_limit='memory_swap',
  881. mem_swappiness='memory_swappiness',
  882. oom_score_adj='oom_score_adj',
  883. shm_size='shm_size',
  884. group_add='groups',
  885. devices='devices',
  886. pid_mode='pid_mode'
  887. )
  888. params = dict()
  889. for key, value in host_config_params.items():
  890. if getattr(self, value, None) is not None:
  891. params[key] = getattr(self, value)
  892. if self.restart_policy:
  893. params['restart_policy'] = dict(Name=self.restart_policy,
  894. MaximumRetryCount=self.restart_retries)
  895. return self.client.create_host_config(**params)
  896. @property
  897. def default_host_ip(self):
  898. ip = '0.0.0.0'
  899. if not self.networks:
  900. return ip
  901. for net in self.networks:
  902. if net.get('name'):
  903. network = self.client.inspect_network(net['name'])
  904. if network.get('Driver') == 'bridge' and \
  905. network.get('Options', {}).get('com.docker.network.bridge.host_binding_ipv4'):
  906. ip = network['Options']['com.docker.network.bridge.host_binding_ipv4']
  907. break
  908. return ip
  909. def _parse_publish_ports(self):
  910. '''
  911. Parse ports from docker CLI syntax
  912. '''
  913. if self.published_ports is None:
  914. return None
  915. if 'all' in self.published_ports:
  916. return 'all'
  917. default_ip = self.default_host_ip
  918. binds = {}
  919. for port in self.published_ports:
  920. parts = str(port).split(':')
  921. container_port = parts[-1]
  922. if '/' not in container_port:
  923. container_port = int(parts[-1])
  924. p_len = len(parts)
  925. if p_len == 1:
  926. bind = (default_ip,)
  927. elif p_len == 2:
  928. bind = (default_ip, int(parts[0]))
  929. elif p_len == 3:
  930. bind = (parts[0], int(parts[1])) if parts[1] else (parts[0],)
  931. if container_port in binds:
  932. old_bind = binds[container_port]
  933. if isinstance(old_bind, list):
  934. old_bind.append(bind)
  935. else:
  936. binds[container_port] = [binds[container_port], bind]
  937. else:
  938. binds[container_port] = bind
  939. return binds
  940. @staticmethod
  941. def _get_volume_binds(volumes):
  942. '''
  943. Extract host bindings, if any, from list of volume mapping strings.
  944. :return: dictionary of bind mappings
  945. '''
  946. result = dict()
  947. if volumes:
  948. for vol in volumes:
  949. host = None
  950. if ':' in vol:
  951. if len(vol.split(':')) == 3:
  952. host, container, mode = vol.split(':')
  953. if len(vol.split(':')) == 2:
  954. parts = vol.split(':')
  955. if parts[1] not in VOLUME_PERMISSIONS:
  956. host, container, mode = (vol.split(':') + ['rw'])
  957. if host is not None:
  958. result[host] = dict(
  959. bind=container,
  960. mode=mode
  961. )
  962. return result
  963. def _parse_exposed_ports(self, published_ports):
  964. '''
  965. Parse exposed ports from docker CLI-style ports syntax.
  966. '''
  967. exposed = []
  968. if self.exposed_ports:
  969. for port in self.exposed_ports:
  970. port = str(port).strip()
  971. protocol = 'tcp'
  972. match = re.search(r'(/.+$)', port)
  973. if match:
  974. protocol = match.group(1).replace('/', '')
  975. port = re.sub(r'/.+$', '', port)
  976. exposed.append((port, protocol))
  977. if published_ports:
  978. # Any published port should also be exposed
  979. for publish_port in published_ports:
  980. match = False
  981. if isinstance(publish_port, basestring) and '/' in publish_port:
  982. port, protocol = publish_port.split('/')
  983. port = int(port)
  984. else:
  985. protocol = 'tcp'
  986. port = int(publish_port)
  987. for exposed_port in exposed:
  988. if isinstance(exposed_port[0], basestring) and '-' in exposed_port[0]:
  989. start_port, end_port = exposed_port[0].split('-')
  990. if int(start_port) <= port <= int(end_port):
  991. match = True
  992. elif exposed_port[0] == port:
  993. match = True
  994. if not match:
  995. exposed.append((port, protocol))
  996. return exposed
  997. @staticmethod
  998. def _parse_links(links):
  999. '''
  1000. Turn links into a dictionary
  1001. '''
  1002. if links is None:
  1003. return None
  1004. result = {}
  1005. for link in links:
  1006. parsed_link = link.split(':', 1)
  1007. if len(parsed_link) == 2:
  1008. result[parsed_link[0]] = parsed_link[1]
  1009. else:
  1010. result[parsed_link[0]] = parsed_link[0]
  1011. return result
  1012. def _parse_ulimits(self):
  1013. '''
  1014. Turn ulimits into an array of Ulimit objects
  1015. '''
  1016. if self.ulimits is None:
  1017. return None
  1018. results = []
  1019. for limit in self.ulimits:
  1020. limits = dict()
  1021. pieces = limit.split(':')
  1022. if len(pieces) >= 2:
  1023. limits['name'] = pieces[0]
  1024. limits['soft'] = int(pieces[1])
  1025. limits['hard'] = int(pieces[1])
  1026. if len(pieces) == 3:
  1027. limits['hard'] = int(pieces[2])
  1028. try:
  1029. results.append(Ulimit(**limits))
  1030. except ValueError as exc:
  1031. self.fail("Error parsing ulimits value %s - %s" % (limit, exc))
  1032. return results
  1033. def _parse_log_config(self):
  1034. '''
  1035. Create a LogConfig object
  1036. '''
  1037. if self.log_driver is None:
  1038. return None
  1039. options = dict(
  1040. Type=self.log_driver,
  1041. Config = dict()
  1042. )
  1043. if self.log_options is not None:
  1044. options['Config'] = self.log_options
  1045. try:
  1046. return LogConfig(**options)
  1047. except ValueError as exc:
  1048. self.fail('Error parsing logging options - %s' % (exc))
  1049. def _get_environment(self):
  1050. """
  1051. If environment file is combined with explicit environment variables, the explicit environment variables
  1052. take precedence.
  1053. """
  1054. final_env = {}
  1055. if self.env_file:
  1056. parsed_env_file = utils.parse_env_file(self.env_file)
  1057. for name, value in parsed_env_file.items():
  1058. final_env[name] = str(value)
  1059. if self.env:
  1060. for name, value in self.env.items():
  1061. final_env[name] = str(value)
  1062. return final_env
  1063. def _get_network_id(self, network_name):
  1064. network_id = None
  1065. try:
  1066. for network in self.client.networks(names=[network_name]):
  1067. if network['Name'] == network_name:
  1068. network_id = network['Id']
  1069. break
  1070. except Exception as exc:
  1071. self.fail("Error getting network id for %s - %s" % (network_name, str(exc)))
  1072. return network_id
  1073. class Container(DockerBaseClass):
  1074. def __init__(self, container, parameters):
  1075. super(Container, self).__init__()
  1076. self.raw = container
  1077. self.Id = None
  1078. self.container = container
  1079. if container:
  1080. self.Id = container['Id']
  1081. self.Image = container['Image']
  1082. self.log(self.container, pretty_print=True)
  1083. self.parameters = parameters
  1084. self.parameters.expected_links = None
  1085. self.parameters.expected_ports = None
  1086. self.parameters.expected_exposed = None
  1087. self.parameters.expected_volumes = None
  1088. self.parameters.expected_ulimits = None
  1089. self.parameters.expected_etc_hosts = None
  1090. self.parameters.expected_env = None
  1091. def fail(self, msg):
  1092. self.parameters.client.module.fail_json(msg=msg)
  1093. @property
  1094. def exists(self):
  1095. return True if self.container else False
  1096. @property
  1097. def running(self):
  1098. if self.container and self.container.get('State'):
  1099. if self.container['State'].get('Running') and not self.container['State'].get('Ghost', False):
  1100. return True
  1101. return False
  1102. def has_different_configuration(self, image):
  1103. '''
  1104. Diff parameters vs existing container config. Returns tuple: (True | False, List of differences)
  1105. '''
  1106. self.log('Starting has_different_configuration')
  1107. self.parameters.expected_entrypoint = self._get_expected_entrypoint()
  1108. self.parameters.expected_links = self._get_expected_links()
  1109. self.parameters.expected_ports = self._get_expected_ports()
  1110. self.parameters.expected_exposed = self._get_expected_exposed(image)
  1111. self.parameters.expected_volumes = self._get_expected_volumes(image)
  1112. self.parameters.expected_binds = self._get_expected_binds(image)
  1113. self.parameters.expected_ulimits = self._get_expected_ulimits(self.parameters.ulimits)
  1114. self.parameters.expected_etc_hosts = self._convert_simple_dict_to_list('etc_hosts')
  1115. self.parameters.expected_env = self._get_expected_env(image)
  1116. self.parameters.expected_cmd = self._get_expected_cmd()
  1117. self.parameters.expected_devices = self._get_expected_devices()
  1118. if not self.container.get('HostConfig'):
  1119. self.fail("has_config_diff: Error parsing container properties. HostConfig missing.")
  1120. if not self.container.get('Config'):
  1121. self.fail("has_config_diff: Error parsing container properties. Config missing.")
  1122. if not self.container.get('NetworkSettings'):
  1123. self.fail("has_config_diff: Error parsing container properties. NetworkSettings missing.")
  1124. host_config = self.container['HostConfig']
  1125. log_config = host_config.get('LogConfig', dict())
  1126. restart_policy = host_config.get('RestartPolicy', dict())
  1127. config = self.container['Config']
  1128. network = self.container['NetworkSettings']
  1129. # The previous version of the docker module ignored the detach state by
  1130. # assuming if the container was running, it must have been detached.
  1131. detach = not (config.get('AttachStderr') and config.get('AttachStdout'))
  1132. # "ExposedPorts": null returns None type & causes AttributeError - PR #5517
  1133. if config.get('ExposedPorts') is not None:
  1134. expected_exposed = [re.sub(r'/.+$', '', p) for p in config.get('ExposedPorts', dict()).keys()]
  1135. else:
  1136. expected_exposed = []
  1137. # Map parameters to container inspect results
  1138. config_mapping = dict(
  1139. image=config.get('Image'),
  1140. expected_cmd=config.get('Cmd'),
  1141. hostname=config.get('Hostname'),
  1142. user=config.get('User'),
  1143. detach=detach,
  1144. interactive=config.get('OpenStdin'),
  1145. capabilities=host_config.get('CapAdd'),
  1146. expected_devices=host_config.get('Devices'),
  1147. dns_servers=host_config.get('Dns'),
  1148. dns_opts=host_config.get('DnsOptions'),
  1149. dns_search_domains=host_config.get('DnsSearch'),
  1150. expected_env=(config.get('Env') or []),
  1151. expected_entrypoint=config.get('Entrypoint'),
  1152. expected_etc_hosts=host_config['ExtraHosts'],
  1153. expected_exposed=expected_exposed,
  1154. groups=host_config.get('GroupAdd'),
  1155. ipc_mode=host_config.get("IpcMode"),
  1156. labels=config.get('Labels'),
  1157. expected_links=host_config.get('Links'),
  1158. log_driver=log_config.get('Type'),
  1159. log_options=log_config.get('Config'),
  1160. mac_address=network.get('MacAddress'),
  1161. memory_swappiness=host_config.get('MemorySwappiness'),
  1162. network_mode=host_config.get('NetworkMode'),
  1163. oom_killer=host_config.get('OomKillDisable'),
  1164. oom_score_adj=host_config.get('OomScoreAdj'),
  1165. pid_mode=host_config.get('PidMode'),
  1166. privileged=host_config.get('Privileged'),
  1167. expected_ports=host_config.get('PortBindings'),
  1168. read_only=host_config.get('ReadonlyRootfs'),
  1169. restart_policy=restart_policy.get('Name'),
  1170. restart_retries=restart_policy.get('MaximumRetryCount'),
  1171. # Cannot test shm_size, as shm_size is not included in container inspection results.
  1172. # shm_size=host_config.get('ShmSize'),
  1173. security_opts=host_config.get("SecuriytOpt"),
  1174. stop_signal=config.get("StopSignal"),
  1175. tty=config.get('Tty'),
  1176. expected_ulimits=host_config.get('Ulimits'),
  1177. uts=host_config.get('UTSMode'),
  1178. expected_volumes=config.get('Volumes'),
  1179. expected_binds=host_config.get('Binds'),
  1180. volumes_from=host_config.get('VolumesFrom'),
  1181. volume_driver=host_config.get('VolumeDriver')
  1182. )
  1183. differences = []
  1184. for key, value in config_mapping.items():
  1185. self.log('check differences %s %s vs %s' % (key, getattr(self.parameters, key), str(value)))
  1186. if getattr(self.parameters, key, None) is not None:
  1187. if isinstance(getattr(self.parameters, key), list) and isinstance(value, list):
  1188. if len(getattr(self.parameters, key)) > 0 and isinstance(getattr(self.parameters, key)[0], dict):
  1189. # compare list of dictionaries
  1190. self.log("comparing list of dict: %s" % key)
  1191. match = self._compare_dictionary_lists(getattr(self.parameters, key), value)
  1192. else:
  1193. # compare two lists. Is list_a in list_b?
  1194. self.log("comparing lists: %s" % key)
  1195. set_a = set(getattr(self.parameters, key))
  1196. set_b = set(value)
  1197. match = (set_a <= set_b)
  1198. elif isinstance(getattr(self.parameters, key), dict) and isinstance(value, dict):
  1199. # compare two dicts
  1200. self.log("comparing two dicts: %s" % key)
  1201. match = self._compare_dicts(getattr(self.parameters, key), value)
  1202. else:
  1203. # primitive compare
  1204. self.log("primitive compare: %s" % key)
  1205. match = (getattr(self.parameters, key) == value)
  1206. if not match:
  1207. # no match. record the differences
  1208. item = dict()
  1209. item[key] = dict(
  1210. parameter=getattr(self.parameters, key),
  1211. container=value
  1212. )
  1213. differences.append(item)
  1214. has_differences = True if len(differences) > 0 else False
  1215. return has_differences, differences
  1216. def _compare_dictionary_lists(self, list_a, list_b):
  1217. '''
  1218. If all of list_a exists in list_b, return True
  1219. '''
  1220. if not isinstance(list_a, list) or not isinstance(list_b, list):
  1221. return False
  1222. matches = 0
  1223. for dict_a in list_a:
  1224. for dict_b in list_b:
  1225. if self._compare_dicts(dict_a, dict_b):
  1226. matches += 1
  1227. break
  1228. result = (matches == len(list_a))
  1229. return result
  1230. def _compare_dicts(self, dict_a, dict_b):
  1231. '''
  1232. If dict_a in dict_b, return True
  1233. '''
  1234. if not isinstance(dict_a, dict) or not isinstance(dict_b, dict):
  1235. return False
  1236. for key, value in dict_a.items():
  1237. if isinstance(value, dict):
  1238. match = self._compare_dicts(value, dict_b.get(key))
  1239. elif isinstance(value, list):
  1240. if len(value) > 0 and isinstance(value[0], dict):
  1241. match = self._compare_dictionary_lists(value, dict_b.get(key))
  1242. else:
  1243. set_a = set(value)
  1244. set_b = set(dict_b.get(key))
  1245. match = (set_a == set_b)
  1246. else:
  1247. match = (value == dict_b.get(key))
  1248. if not match:
  1249. return False
  1250. return True
  1251. def has_different_resource_limits(self):
  1252. '''
  1253. Diff parameters and container resource limits
  1254. '''
  1255. if not self.container.get('HostConfig'):
  1256. self.fail("limits_differ_from_container: Error parsing container properties. HostConfig missing.")
  1257. host_config = self.container['HostConfig']
  1258. config_mapping = dict(
  1259. cpu_period=host_config.get('CpuPeriod'),
  1260. cpu_quota=host_config.get('CpuQuota'),
  1261. cpuset_cpus=host_config.get('CpusetCpus'),
  1262. cpuset_mems=host_config.get('CpusetMems'),
  1263. cpu_shares=host_config.get('CpuShares'),
  1264. kernel_memory=host_config.get("KernelMemory"),
  1265. memory=host_config.get('Memory'),
  1266. memory_reservation=host_config.get('MemoryReservation'),
  1267. memory_swap=host_config.get('MemorySwap'),
  1268. oom_score_adj=host_config.get('OomScoreAdj'),
  1269. )
  1270. differences = []
  1271. for key, value in config_mapping.items():
  1272. if getattr(self.parameters, key, None) and getattr(self.parameters, key) != value:
  1273. # no match. record the differences
  1274. item = dict()
  1275. item[key] = dict(
  1276. parameter=getattr(self.parameters, key),
  1277. container=value
  1278. )
  1279. differences.append(item)
  1280. different = (len(differences) > 0)
  1281. return different, differences
  1282. def has_network_differences(self):
  1283. '''
  1284. Check if the container is connected to requested networks with expected options: links, aliases, ipv4, ipv6
  1285. '''
  1286. different = False
  1287. differences = []
  1288. if not self.parameters.networks:
  1289. return different, differences
  1290. if not self.container.get('NetworkSettings'):
  1291. self.fail("has_missing_networks: Error parsing container properties. NetworkSettings missing.")
  1292. connected_networks = self.container['NetworkSettings']['Networks']
  1293. for network in self.parameters.networks:
  1294. if connected_networks.get(network['name'], None) is None:
  1295. different = True
  1296. differences.append(dict(
  1297. parameter=network,
  1298. container=None
  1299. ))
  1300. else:
  1301. diff = False
  1302. if network.get('ipv4_address') and network['ipv4_address'] != connected_networks[network['name']].get('IPAddress'):
  1303. diff = True
  1304. if network.get('ipv6_address') and network['ipv6_address'] != connected_networks[network['name']].get('GlobalIPv6Address'):
  1305. diff = True
  1306. if network.get('aliases') and not connected_networks[network['name']].get('Aliases'):
  1307. diff = True
  1308. if network.get('aliases') and connected_networks[network['name']].get('Aliases'):
  1309. for alias in network.get('aliases'):
  1310. if alias not in connected_networks[network['name']].get('Aliases', []):
  1311. diff = True
  1312. if network.get('links') and not connected_networks[network['name']].get('Links'):
  1313. diff = True
  1314. if network.get('links') and connected_networks[network['name']].get('Links'):
  1315. expected_links = []
  1316. for link, alias in network['links'].items():
  1317. expected_links.append("%s:%s" % (link, alias))
  1318. for link in expected_links:
  1319. if link not in connected_networks[network['name']].get('Links', []):
  1320. diff = True
  1321. if diff:
  1322. different = True
  1323. differences.append(dict(
  1324. parameter=network,
  1325. container=dict(
  1326. name=network['name'],
  1327. ipv4_address=connected_networks[network['name']].get('IPAddress'),
  1328. ipv6_address=connected_networks[network['name']].get('GlobalIPv6Address'),
  1329. aliases=connected_networks[network['name']].get('Aliases'),
  1330. links=connected_networks[network['name']].get('Links')
  1331. )
  1332. ))
  1333. return different, differences
  1334. def has_extra_networks(self):
  1335. '''
  1336. Check if the container is connected to non-requested networks
  1337. '''
  1338. extra_networks = []
  1339. extra = False
  1340. if not self.container.get('NetworkSettings'):
  1341. self.fail("has_extra_networks: Error parsing container properties. NetworkSettings missing.")
  1342. connected_networks = self.container['NetworkSettings'].get('Networks')
  1343. if connected_networks:
  1344. for network, network_config in connected_networks.items():
  1345. keep = False
  1346. if self.parameters.networks:
  1347. for expected_network in self.parameters.networks:
  1348. if expected_network['name'] == network:
  1349. keep = True
  1350. if not keep:
  1351. extra = True
  1352. extra_networks.append(dict(name=network, id=network_config['NetworkID']))
  1353. return extra, extra_networks
  1354. def _get_expected_devices(self):
  1355. if not self.parameters.devices:
  1356. return None
  1357. expected_devices = []
  1358. for device in self.parameters.devices:
  1359. parts = device.split(':')
  1360. if len(parts) == 1:
  1361. expected_devices.append(
  1362. dict(
  1363. CgroupPermissions='rwm',
  1364. PathInContainer=parts[0],
  1365. PathOnHost=parts[0]
  1366. ))
  1367. elif len(parts) == 2:
  1368. parts = device.split(':')
  1369. expected_devices.append(
  1370. dict(
  1371. CgroupPermissions='rwm',
  1372. PathInContainer=parts[1],
  1373. PathOnHost=parts[0]
  1374. )
  1375. )
  1376. else:
  1377. expected_devices.append(
  1378. dict(
  1379. CgroupPermissions=parts[2],
  1380. PathInContainer=parts[1],
  1381. PathOnHost=parts[0]
  1382. ))
  1383. return expected_devices
  1384. def _get_expected_entrypoint(self):
  1385. self.log('_get_expected_entrypoint')
  1386. if not self.parameters.entrypoint:
  1387. return None
  1388. return shlex.split(self.parameters.entrypoint)
  1389. def _get_expected_ports(self):
  1390. if not self.parameters.published_ports:
  1391. return None
  1392. expected_bound_ports = {}
  1393. for container_port, config in self.parameters.published_ports.items():
  1394. if isinstance(container_port, int):
  1395. container_port = "%s/tcp" % container_port
  1396. if len(config) == 1:
  1397. expected_bound_ports[container_port] = [{'HostIp': "0.0.0.0", 'HostPort': ""}]
  1398. elif isinstance(config[0], tuple):
  1399. expected_bound_ports[container_port] = []
  1400. for host_ip, host_port in config:
  1401. expected_bound_ports[container_port].append({'HostIp': host_ip, 'HostPort': str(host_port)})
  1402. else:
  1403. expected_bound_ports[container_port] = [{'HostIp': config[0], 'HostPort': str(config[1])}]
  1404. return expected_bound_ports
  1405. def _get_expected_links(self):
  1406. if self.parameters.links is None:
  1407. return None
  1408. self.log('parameter links:')
  1409. self.log(self.parameters.links, pretty_print=True)
  1410. exp_links = []
  1411. for link, alias in self.parameters.links.items():
  1412. exp_links.append("/%s:%s/%s" % (link, ('/' + self.parameters.name), alias))
  1413. return exp_links
  1414. def _get_expected_binds(self, image):
  1415. self.log('_get_expected_binds')
  1416. image_vols = []
  1417. if image:
  1418. image_vols = self._get_image_binds(image['ContainerConfig'].get('Volumes'))
  1419. param_vols = []
  1420. if self.parameters.volumes:
  1421. for vol in self.parameters.volumes:
  1422. host = None
  1423. if ':' in vol:
  1424. if len(vol.split(':')) == 3:
  1425. host, container, mode = vol.split(':')
  1426. if len(vol.split(':')) == 2:
  1427. parts = vol.split(':')
  1428. if parts[1] not in VOLUME_PERMISSIONS:
  1429. host, container, mode = vol.split(':') + ['rw']
  1430. if host:
  1431. param_vols.append("%s:%s:%s" % (host, container, mode))
  1432. result = list(set(image_vols + param_vols))
  1433. self.log("expected_binds:")
  1434. self.log(result, pretty_print=True)
  1435. return result
  1436. def _get_image_binds(self, volumes):
  1437. '''
  1438. Convert array of binds to array of strings with format host_path:container_path:mode
  1439. :param volumes: array of bind dicts
  1440. :return: array of strings
  1441. '''
  1442. results = []
  1443. if isinstance(volumes, dict):
  1444. results += self._get_bind_from_dict(volumes)
  1445. elif isinstance(volumes, list):
  1446. for vol in volumes:
  1447. results += self._get_bind_from_dict(vol)
  1448. return results
  1449. @staticmethod
  1450. def _get_bind_from_dict(volume_dict):
  1451. results = []
  1452. if volume_dict:
  1453. for host_path, config in volume_dict.items():
  1454. if isinstance(config, dict) and config.get('bind'):
  1455. container_path = config.get('bind')
  1456. mode = config.get('mode', 'rw')
  1457. results.append("%s:%s:%s" % (host_path, container_path, mode))
  1458. return results
  1459. def _get_expected_volumes(self, image):
  1460. self.log('_get_expected_volumes')
  1461. expected_vols = dict()
  1462. if image and image['ContainerConfig'].get('Volumes'):
  1463. expected_vols.update(image['ContainerConfig'].get('Volumes'))
  1464. if self.parameters.volumes:
  1465. for vol in self.parameters.volumes:
  1466. container = None
  1467. if ':' in vol:
  1468. if len(vol.split(':')) == 3:
  1469. host, container, mode = vol.split(':')
  1470. if len(vol.split(':')) == 2:
  1471. parts = vol.split(':')
  1472. if parts[1] not in VOLUME_PERMISSIONS:
  1473. host, container, mode = vol.split(':') + ['rw']
  1474. new_vol = dict()
  1475. if container:
  1476. new_vol[container] = dict()
  1477. else:
  1478. new_vol[vol] = dict()
  1479. expected_vols.update(new_vol)
  1480. if not expected_vols:
  1481. expected_vols = None
  1482. self.log("expected_volumes:")
  1483. self.log(expected_vols, pretty_print=True)
  1484. return expected_vols
  1485. def _get_expected_env(self, image):
  1486. self.log('_get_expected_env')
  1487. expected_env = dict()
  1488. if image and image['ContainerConfig'].get('Env'):
  1489. for env_var in image['ContainerConfig']['Env']:
  1490. parts = env_var.split('=', 1)
  1491. expected_env[parts[0]] = parts[1]
  1492. if self.parameters.env:
  1493. expected_env.update(self.parameters.env)
  1494. param_env = []
  1495. for key, value in expected_env.items():
  1496. param_env.append("%s=%s" % (key, value))
  1497. return param_env
  1498. def _get_expected_exposed(self, image):
  1499. self.log('_get_expected_exposed')
  1500. image_ports = []
  1501. if image:
  1502. image_ports = [re.sub(r'/.+$', '', p) for p in (image['ContainerConfig'].get('ExposedPorts') or {}).keys()]
  1503. param_ports = []
  1504. if self.parameters.ports:
  1505. param_ports = [str(p[0]) for p in self.parameters.ports]
  1506. result = list(set(image_ports + param_ports))
  1507. self.log(result, pretty_print=True)
  1508. return result
  1509. def _get_expected_ulimits(self, config_ulimits):
  1510. self.log('_get_expected_ulimits')
  1511. if config_ulimits is None:
  1512. return None
  1513. results = []
  1514. for limit in config_ulimits:
  1515. results.append(dict(
  1516. Name=limit.name,
  1517. Soft=limit.soft,
  1518. Hard=limit.hard
  1519. ))
  1520. return results
  1521. def _get_expected_cmd(self):
  1522. self.log('_get_expected_cmd')
  1523. if not self.parameters.command:
  1524. return None
  1525. return shlex.split(self.parameters.command)
  1526. def _convert_simple_dict_to_list(self, param_name, join_with=':'):
  1527. if getattr(self.parameters, param_name, None) is None:
  1528. return None
  1529. results = []
  1530. for key, value in getattr(self.parameters, param_name).items():
  1531. results.append("%s%s%s" % (key, join_with, value))
  1532. return results
  1533. class ContainerManager(DockerBaseClass):
  1534. '''
  1535. Perform container management tasks
  1536. '''
  1537. def __init__(self, client):
  1538. super(ContainerManager, self).__init__()
  1539. self.client = client
  1540. self.parameters = TaskParameters(client)
  1541. self.check_mode = self.client.check_mode
  1542. self.results = {'changed': False, 'actions': []}
  1543. self.diff = {}
  1544. self.facts = {}
  1545. state = self.parameters.state
  1546. if state in ('stopped', 'started', 'present'):
  1547. self.present(state)
  1548. elif state == 'absent':
  1549. self.absent()
  1550. if not self.check_mode and not self.parameters.debug:
  1551. self.results.pop('actions')
  1552. if self.client.module._diff or self.parameters.debug:
  1553. self.results['diff'] = self.diff
  1554. if self.facts:
  1555. self.results['ansible_facts'] = {'docker_container': self.facts}
  1556. def present(self, state):
  1557. container = self._get_container(self.parameters.name)
  1558. image = self._get_image()
  1559. if not container.exists:
  1560. # New container
  1561. self.log('No container found')
  1562. new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
  1563. if new_container:
  1564. container = new_container
  1565. else:
  1566. # Existing container
  1567. different, differences = container.has_different_configuration(image)
  1568. image_different = False
  1569. if not self.parameters.ignore_image:
  1570. image_different = self._image_is_different(image, container)
  1571. if image_different or different or self.parameters.recreate:
  1572. self.diff['differences'] = differences
  1573. if image_different:
  1574. self.diff['image_different'] = True
  1575. self.log("differences")
  1576. self.log(differences, pretty_print=True)
  1577. if container.running:
  1578. self.container_stop(container.Id)
  1579. self.container_remove(container.Id)
  1580. new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
  1581. if new_container:
  1582. container = new_container
  1583. if container and container.exists:
  1584. container = self.update_limits(container)
  1585. container = self.update_networks(container)
  1586. if state == 'started' and not container.running:
  1587. container = self.container_start(container.Id)
  1588. elif state == 'started' and self.parameters.restart:
  1589. self.container_stop(container.Id)
  1590. container = self.container_start(container.Id)
  1591. elif state == 'stopped' and container.running:
  1592. self.container_stop(container.Id)
  1593. container = self._get_container(container.Id)
  1594. self.facts = container.raw
  1595. def absent(self):
  1596. container = self._get_container(self.parameters.name)
  1597. if container.exists:
  1598. if container.running:
  1599. self.container_stop(container.Id)
  1600. self.container_remove(container.Id)
  1601. def fail(self, msg, **kwargs):
  1602. self.client.module.fail_json(msg=msg, **kwargs)
  1603. def _get_container(self, container):
  1604. '''
  1605. Expects container ID or Name. Returns a container object
  1606. '''
  1607. return Container(self.client.get_container(container), self.parameters)
  1608. def _get_image(self):
  1609. if not self.parameters.image:
  1610. self.log('No image specified')
  1611. return None
  1612. repository, tag = utils.parse_repository_tag(self.parameters.image)
  1613. if not tag:
  1614. tag = "latest"
  1615. image = self.client.find_image(repository, tag)
  1616. if not self.check_mode:
  1617. if not image or self.parameters.pull:
  1618. self.log("Pull the image.")
  1619. image, alreadyToLatest = self.client.pull_image(repository, tag)
  1620. if alreadyToLatest:
  1621. self.results['changed'] = False
  1622. else:
  1623. self.results['changed'] = True
  1624. self.results['actions'].append(dict(pulled_image="%s:%s" % (repository, tag)))
  1625. self.log("image")
  1626. self.log(image, pretty_print=True)
  1627. return image
  1628. def _image_is_different(self, image, container):
  1629. if image and image.get('Id'):
  1630. if container and container.Image:
  1631. if image.get('Id') != container.Image:
  1632. return True
  1633. return False
  1634. def update_limits(self, container):
  1635. limits_differ, different_limits = container.has_different_resource_limits()
  1636. if limits_differ:
  1637. self.log("limit differences:")
  1638. self.log(different_limits, pretty_print=True)
  1639. if limits_differ and not self.check_mode:
  1640. self.container_update(container.Id, self.parameters.update_parameters)
  1641. return self._get_container(container.Id)
  1642. return container
  1643. def update_networks(self, container):
  1644. has_network_differences, network_differences = container.has_network_differences()
  1645. updated_container = container
  1646. if has_network_differences:
  1647. if self.diff.get('differences'):
  1648. self.diff['differences'].append(dict(network_differences=network_differences))
  1649. else:
  1650. self.diff['differences'] = [dict(network_differences=network_differences)]
  1651. self.results['changed'] = True
  1652. updated_container = self._add_networks(container, network_differences)
  1653. if self.parameters.purge_networks:
  1654. has_extra_networks, extra_networks = container.has_extra_networks()
  1655. if has_extra_networks:
  1656. if self.diff.get('differences'):
  1657. self.diff['differences'].append(dict(purge_networks=extra_networks))
  1658. else:
  1659. self.diff['differences'] = [dict(purge_networks=extra_networks)]
  1660. self.results['changed'] = True
  1661. updated_container = self._purge_networks(container, extra_networks)
  1662. return updated_container
  1663. def _add_networks(self, container, differences):
  1664. for diff in differences:
  1665. # remove the container from the network, if connected
  1666. if diff.get('container'):
  1667. self.results['actions'].append(dict(removed_from_network=diff['parameter']['name']))
  1668. if not self.check_mode:
  1669. try:
  1670. self.client.disconnect_container_from_network(container.Id, diff['parameter']['id'])
  1671. except Exception as exc:
  1672. self.fail("Error disconnecting container from network %s - %s" % (diff['parameter']['name'],
  1673. str(exc)))
  1674. # connect to the network
  1675. params = dict(
  1676. ipv4_address=diff['parameter'].get('ipv4_address', None),
  1677. ipv6_address=diff['parameter'].get('ipv6_address', None),
  1678. links=diff['parameter'].get('links', None),
  1679. aliases=diff['parameter'].get('aliases', None)
  1680. )
  1681. self.results['actions'].append(dict(added_to_network=diff['parameter']['name'], network_parameters=params))
  1682. if not self.check_mode:
  1683. try:
  1684. self.log("Connecting container to network %s" % diff['parameter']['id'])
  1685. self.log(params, pretty_print=True)
  1686. self.client.connect_container_to_network(container.Id, diff['parameter']['id'], **params)
  1687. except Exception as exc:
  1688. self.fail("Error connecting container to network %s - %s" % (diff['parameter']['name'], str(exc)))
  1689. return self._get_container(container.Id)
  1690. def _purge_networks(self, container, networks):
  1691. for network in networks:
  1692. self.results['actions'].append(dict(removed_from_network=network['name']))
  1693. if not self.check_mode:
  1694. try:
  1695. self.client.disconnect_container_from_network(container.Id, network['name'])
  1696. except Exception as exc:
  1697. self.fail("Error disconnecting container from network %s - %s" % (network['name'],
  1698. str(exc)))
  1699. return self._get_container(container.Id)
  1700. def container_create(self, image, create_parameters):
  1701. self.log("create container")
  1702. self.log("image: %s parameters:" % image)
  1703. self.log(create_parameters, pretty_print=True)
  1704. self.results['actions'].append(dict(created="Created container", create_parameters=create_parameters))
  1705. self.results['changed'] = True
  1706. new_container = None
  1707. if not self.check_mode:
  1708. try:
  1709. new_container = self.client.create_container(image, **create_parameters)
  1710. except Exception as exc:
  1711. self.fail("Error creating container: %s" % str(exc))
  1712. return self._get_container(new_container['Id'])
  1713. return new_container
  1714. def container_start(self, container_id):
  1715. self.log("start container %s" % (container_id))
  1716. self.results['actions'].append(dict(started=container_id))
  1717. self.results['changed'] = True
  1718. if not self.check_mode:
  1719. try:
  1720. self.client.start(container=container_id)
  1721. except Exception as exc:
  1722. self.fail("Error starting container %s: %s" % (container_id, str(exc)))
  1723. if not self.parameters.detach:
  1724. status = self.client.wait(container_id)
  1725. output = self.client.logs(container_id, stdout=True, stderr=True, stream=False, timestamps=False)
  1726. if status != 0:
  1727. self.fail(output, status=status)
  1728. if self.parameters.cleanup:
  1729. self.container_remove(container_id, force=True)
  1730. insp = self._get_container(container_id)
  1731. if insp.raw:
  1732. insp.raw['Output'] = output
  1733. else:
  1734. insp.raw = dict(Output=output)
  1735. return insp
  1736. return self._get_container(container_id)
  1737. def container_remove(self, container_id, link=False, force=False):
  1738. volume_state = (not self.parameters.keep_volumes)
  1739. self.log("remove container container:%s v:%s link:%s force%s" % (container_id, volume_state, link, force))
  1740. self.results['actions'].append(dict(removed=container_id, volume_state=volume_state, link=link, force=force))
  1741. self.results['changed'] = True
  1742. response = None
  1743. if not self.check_mode:
  1744. try:
  1745. response = self.client.remove_container(container_id, v=volume_state, link=link, force=force)
  1746. except Exception as exc:
  1747. self.fail("Error removing container %s: %s" % (container_id, str(exc)))
  1748. return response
  1749. def container_update(self, container_id, update_parameters):
  1750. if update_parameters:
  1751. self.log("update container %s" % (container_id))
  1752. self.log(update_parameters, pretty_print=True)
  1753. self.results['actions'].append(dict(updated=container_id, update_parameters=update_parameters))
  1754. self.results['changed'] = True
  1755. if not self.check_mode and callable(getattr(self.client, 'update_container')):
  1756. try:
  1757. self.client.update_container(container_id, **update_parameters)
  1758. except Exception as exc:
  1759. self.fail("Error updating container %s: %s" % (container_id, str(exc)))
  1760. return self._get_container(container_id)
  1761. def container_kill(self, container_id):
  1762. self.results['actions'].append(dict(killed=container_id, signal=self.parameters.kill_signal))
  1763. self.results['changed'] = True
  1764. response = None
  1765. if not self.check_mode:
  1766. try:
  1767. if self.parameters.kill_signal:
  1768. response = self.client.kill(container_id, signal=self.parameters.kill_signal)
  1769. else:
  1770. response = self.client.kill(container_id)
  1771. except Exception as exc:
  1772. self.fail("Error killing container %s: %s" % (container_id, exc))
  1773. return response
  1774. def container_stop(self, container_id):
  1775. if self.parameters.force_kill:
  1776. self.container_kill(container_id)
  1777. return
  1778. self.results['actions'].append(dict(stopped=container_id, timeout=self.parameters.stop_timeout))
  1779. self.results['changed'] = True
  1780. response = None
  1781. if not self.check_mode:
  1782. try:
  1783. if self.parameters.stop_timeout:
  1784. response = self.client.stop(container_id, timeout=self.parameters.stop_timeout)
  1785. else:
  1786. response = self.client.stop(container_id)
  1787. except Exception as exc:
  1788. self.fail("Error stopping container %s: %s" % (container_id, str(exc)))
  1789. return response
  1790. def main():
  1791. argument_spec = dict(
  1792. blkio_weight=dict(type='int'),
  1793. capabilities=dict(type='list'),
  1794. cleanup=dict(type='bool', default=False),
  1795. command=dict(type='str'),
  1796. cpu_period=dict(type='int'),
  1797. cpu_quota=dict(type='int'),
  1798. cpuset_cpus=dict(type='str'),
  1799. cpuset_mems=dict(type='str'),
  1800. cpu_shares=dict(type='int'),
  1801. detach=dict(type='bool', default=True),
  1802. devices=dict(type='list'),
  1803. dns_servers=dict(type='list'),
  1804. dns_opts=dict(type='list'),
  1805. dns_search_domains=dict(type='list'),
  1806. env=dict(type='dict'),
  1807. env_file=dict(type='path'),
  1808. entrypoint=dict(type='str'),
  1809. etc_hosts=dict(type='dict'),
  1810. exposed_ports=dict(type='list', aliases=['exposed', 'expose']),
  1811. force_kill=dict(type='bool', default=False, aliases=['forcekill']),
  1812. groups=dict(type='list'),
  1813. hostname=dict(type='str'),
  1814. ignore_image=dict(type='bool', default=False),
  1815. image=dict(type='str'),
  1816. interactive=dict(type='bool', default=False),
  1817. ipc_mode=dict(type='str'),
  1818. keep_volumes=dict(type='bool', default=True),
  1819. kernel_memory=dict(type='str'),
  1820. kill_signal=dict(type='str'),
  1821. labels=dict(type='dict'),
  1822. links=dict(type='list'),
  1823. log_driver=dict(type='str',
  1824. choices=['none', 'json-file', 'syslog', 'journald', 'gelf', 'fluentd', 'awslogs', 'splunk'],
  1825. default=None),
  1826. log_options=dict(type='dict', aliases=['log_opt']),
  1827. mac_address=dict(type='str'),
  1828. memory=dict(type='str', default='0'),
  1829. memory_reservation=dict(type='str'),
  1830. memory_swap=dict(type='str'),
  1831. memory_swappiness=dict(type='int'),
  1832. name=dict(type='str', required=True),
  1833. network_mode=dict(type='str'),
  1834. networks=dict(type='list'),
  1835. oom_killer=dict(type='bool'),
  1836. oom_score_adj=dict(type='int'),
  1837. paused=dict(type='bool', default=False),
  1838. pid_mode=dict(type='str'),
  1839. privileged=dict(type='bool', default=False),
  1840. published_ports=dict(type='list', aliases=['ports']),
  1841. pull=dict(type='bool', default=False),
  1842. purge_networks=dict(type='bool', default=False),
  1843. read_only=dict(type='bool', default=False),
  1844. recreate=dict(type='bool', default=False),
  1845. restart=dict(type='bool', default=False),
  1846. restart_policy=dict(type='str', choices=['no', 'on-failure', 'always', 'unless-stopped']),
  1847. restart_retries=dict(type='int', default=None),
  1848. shm_size=dict(type='str'),
  1849. security_opts=dict(type='list'),
  1850. state=dict(type='str', choices=['absent', 'present', 'started', 'stopped'], default='started'),
  1851. stop_signal=dict(type='str'),
  1852. stop_timeout=dict(type='int'),
  1853. trust_image_content=dict(type='bool', default=False),
  1854. tty=dict(type='bool', default=False),
  1855. ulimits=dict(type='list'),
  1856. user=dict(type='str'),
  1857. uts=dict(type='str'),
  1858. volumes=dict(type='list'),
  1859. volumes_from=dict(type='list'),
  1860. volume_driver=dict(type='str'),
  1861. )
  1862. required_if = [
  1863. ('state', 'present', ['image'])
  1864. ]
  1865. client = AnsibleDockerClient(
  1866. argument_spec=argument_spec,
  1867. required_if=required_if,
  1868. supports_check_mode=True
  1869. )
  1870. cm = ContainerManager(client)
  1871. client.module.exit_json(**cm.results)
  1872. # import module snippets
  1873. from ansible.module_utils.basic import *
  1874. if __name__ == '__main__':
  1875. main()