oc_atomic_container.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # pylint: skip-file
  2. # flake8: noqa
  3. # pylint: disable=wrong-import-position,too-many-branches,invalid-name,no-name-in-module, import-error
  4. import json
  5. import os
  6. from distutils.version import StrictVersion
  7. from ansible.module_utils.basic import AnsibleModule
  8. def _install(module, container, image, values_list):
  9. ''' install a container using atomic CLI. values_list is the list of --set arguments.
  10. container is the name given to the container. image is the image to use for the installation. '''
  11. # NOTE: system-package=no is hardcoded. This should be changed to an option in the future.
  12. args = ['atomic', 'install', '--system', '--system-package=no',
  13. '--name=%s' % container] + values_list + [image]
  14. rc, out, err = module.run_command(args, check_rc=False)
  15. if rc != 0:
  16. return rc, out, err, False
  17. else:
  18. changed = "Extracting" in out or "Copying blob" in out
  19. return rc, out, err, changed
  20. def _uninstall(module, name):
  21. ''' uninstall an atomic container by its name. '''
  22. args = ['atomic', 'uninstall', name]
  23. rc, out, err = module.run_command(args, check_rc=False)
  24. return rc, out, err, False
  25. def _ensure_service_file_is_removed(container):
  26. '''atomic install won't overwrite existing service file, so it needs to be removed'''
  27. service_path = '/etc/systemd/system/{}.service'.format(container)
  28. if not os.path.exists(service_path):
  29. return
  30. os.remove(service_path)
  31. def do_install(module, container, image, values_list):
  32. ''' install a container and exit the module. '''
  33. _ensure_service_file_is_removed(container)
  34. rc, out, err, changed = _install(module, container, image, values_list)
  35. if rc != 0:
  36. module.fail_json(rc=rc, msg=err)
  37. else:
  38. module.exit_json(msg=out, changed=changed)
  39. def do_uninstall(module, name):
  40. ''' uninstall a container and exit the module. '''
  41. rc, out, err, changed = _uninstall(module, name)
  42. if rc != 0:
  43. module.fail_json(rc=rc, msg=err)
  44. module.exit_json(msg=out, changed=changed)
  45. def do_update(module, container, old_image, image, values_list):
  46. ''' update a container and exit the module. If the container uses a different
  47. image than the current installed one, then first uninstall the old one '''
  48. # the image we want is different than the installed one
  49. if old_image != image:
  50. rc, out, err, _ = _uninstall(module, container)
  51. if rc != 0:
  52. module.fail_json(rc=rc, msg=err)
  53. return do_install(module, container, image, values_list)
  54. # if the image didn't change, use "atomic containers update"
  55. args = ['atomic', 'containers', 'update'] + values_list + [container]
  56. rc, out, err = module.run_command(args, check_rc=False)
  57. if rc != 0:
  58. module.fail_json(rc=rc, msg=err)
  59. else:
  60. changed = "Extracting" in out or "Copying blob" in out
  61. module.exit_json(msg=out, changed=changed)
  62. def do_rollback(module, name):
  63. ''' move to the previous deployment of the container, if present, and exit the module. '''
  64. args = ['atomic', 'containers', 'rollback', name]
  65. rc, out, err = module.run_command(args, check_rc=False)
  66. if rc != 0:
  67. module.fail_json(rc=rc, msg=err)
  68. else:
  69. changed = "Rolling back" in out
  70. module.exit_json(msg=out, changed=changed)
  71. def core(module):
  72. ''' entrypoint for the module. '''
  73. name = module.params['name']
  74. image = module.params['image']
  75. values = module.params['values']
  76. state = module.params['state']
  77. module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
  78. out = {}
  79. err = {}
  80. rc = 0
  81. values_list = ["--set=%s" % x for x in values] if values else []
  82. args = ['atomic', 'containers', 'list', '--json', '--all', '-f', 'container=%s' % name]
  83. rc, out, err = module.run_command(args, check_rc=False)
  84. if rc != 0:
  85. module.fail_json(rc=rc, msg=err)
  86. return
  87. # NOTE: "or '[]' is a workaround until atomic containers list --json
  88. # provides an empty list when no containers are present.
  89. containers = json.loads(out or '[]')
  90. present = len(containers) > 0
  91. old_image = containers[0]["image_name"] if present else None
  92. if state == 'present' and present:
  93. module.exit_json(msg=out, changed=False)
  94. elif (state in ['latest', 'present']) and not present:
  95. do_install(module, name, image, values_list)
  96. elif state == 'latest':
  97. do_update(module, name, old_image, image, values_list)
  98. elif state == 'absent':
  99. if not present:
  100. module.exit_json(msg="", changed=False)
  101. else:
  102. do_uninstall(module, name)
  103. elif state == 'rollback':
  104. do_rollback(module, name)
  105. def main():
  106. module = AnsibleModule(
  107. argument_spec=dict(
  108. name=dict(default=None, required=True),
  109. image=dict(default=None, required=True),
  110. state=dict(default='latest', choices=['present', 'absent', 'latest', 'rollback']),
  111. values=dict(type='list', default=[]),
  112. ),
  113. )
  114. # Verify that the platform supports atomic command
  115. rc, version_out, err = module.run_command('rpm -q --queryformat "%{VERSION}\n" atomic', check_rc=False)
  116. if rc != 0:
  117. module.fail_json(msg="Error in running atomic command", err=err)
  118. # This module requires atomic version 1.17.2 or later
  119. atomic_version = StrictVersion(version_out.replace('\n', ''))
  120. if atomic_version < StrictVersion('1.17.2'):
  121. module.fail_json(
  122. msg="atomic version 1.17.2+ is required",
  123. err=str(atomic_version))
  124. try:
  125. core(module)
  126. except Exception as e: # pylint: disable=broad-except
  127. module.fail_json(msg=str(e))
  128. if __name__ == '__main__':
  129. main()