diff --git a/meta/runtime.yml b/meta/runtime.yml index e228ab55..195911d1 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -7,6 +7,7 @@ requires_ansible: '>=2.15.0' action_groups: docker: - docker_compose_v2 + - docker_compose_v2_build - docker_compose_v2_exec - docker_compose_v2_pull - docker_compose_v2_run diff --git a/plugins/modules/docker_compose_v2_build.py b/plugins/modules/docker_compose_v2_build.py new file mode 100644 index 00000000..52c4308f --- /dev/null +++ b/plugins/modules/docker_compose_v2_build.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# +# Copyright (c) 2023, Felix Fontein +# Copyright (c) 2025, Maciej Bogusz (@mjbogusz) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r""" +module: docker_compose_v2_build + +short_description: Build a Docker compose project + +version_added: 4.7.0 + +description: + - Uses Docker Compose to build images for a project. +extends_documentation_fragment: + - community.docker.compose_v2 + - community.docker.compose_v2.minimum_version + - community.docker.docker.cli_documentation + - community.docker.attributes + - community.docker.attributes.actiongroup_docker + +attributes: + check_mode: + support: full + diff_mode: + support: none + idempotent: + support: full + +options: + no_cache: + description: + - If set to V(true), will not use cache when building the images. + type: bool + default: false + pull: + description: + - If set to V(true), will attempt to pull newer version of the image + type: bool + default: false + with_dependencies: + description: + - If set to V(true), also build services that are declared as dependencies. + - This only makes sense if O(services) is used. + type: bool + default: false + memory_limit: + description: + - Memory limit for the build container, in bytes. Not supported by BuildKit + type: int + services: + description: + - Specifies a subset of services to be targeted. + type: list + elements: str + +author: + - Maciej Bogusz (@mjbogusz) + +seealso: + - module: community.docker.docker_compose_v2 +""" + +EXAMPLES = r""" +--- +- name: Build images for flask project + community.docker.docker_compose_v2_build: + project_src: /path/to/flask +""" + +RETURN = r""" +actions: + description: + - A list of actions that have been applied. + returned: success + type: list + elements: dict + contains: + what: + description: + - What kind of resource was changed. + type: str + sample: container + choices: + - image + - unknown + id: + description: + - The ID of the resource that was changed. + type: str + sample: container + status: + description: + - The status change that happened. + type: str + sample: Building + choices: + - Building +""" + +import traceback + +from ansible.module_utils.common.text.converters import to_native + +from ansible_collections.community.docker.plugins.module_utils.common_cli import ( + AnsibleModuleDockerClient, + DockerException, +) + +from ansible_collections.community.docker.plugins.module_utils.compose_v2 import ( + BaseComposeManager, + common_compose_argspec_ex, +) + + +class BuildManager(BaseComposeManager): + def __init__(self, client): + super(BuildManager, self).__init__(client) + parameters = self.client.module.params + + self.no_cache = parameters['no_cache'] + self.pull = parameters['pull'] + self.with_dependencies = parameters['with_dependencies'] + self.memory_limit = parameters['memory_limit'] + self.services = parameters['services'] or [] + + def get_build_cmd(self, dry_run): + args = self.get_base_args() + ['build'] + if self.no_cache: + args.append('--no-cache') + if self.pull: + args.append('--pull') + if self.with_dependencies: + args.append('--with-dependencies') + if self.memory_limit: + args.extend(['--memory', str(self.memory_limit)]) + if dry_run: + args.append('--dry-run') + args.append('--') + for service in self.services: + args.append(service) + return args + + def run(self): + result = dict() + args = self.get_build_cmd(self.check_mode) + rc, stdout, stderr = self.client.call_cli(*args, cwd=self.project_src) + events = self.parse_events(stderr, dry_run=self.check_mode, nonzero_rc=rc != 0) + self.emit_warnings(events) + self.update_result(result, events, stdout, stderr, ignore_ignore_build_events=not self.check_mode) + self.update_failed(result, events, args, stdout, stderr, rc) + self.cleanup_result(result) + return result + + +def main(): + argument_spec = dict( + no_cache=dict(type='bool', default=False), + pull=dict(type='bool', default=False), + with_dependencies=dict(type='bool', default=False), + memory_limit=dict(type='int'), + services=dict(type='list', elements='str'), + ) + argspec_ex = common_compose_argspec_ex() + argument_spec.update(argspec_ex.pop('argspec')) + + client = AnsibleModuleDockerClient( + argument_spec=argument_spec, + supports_check_mode=True, + needs_api_version=False, + **argspec_ex + ) + + try: + manager = BuildManager(client) + result = manager.run() + manager.cleanup() + client.module.exit_json(**result) + except DockerException as e: + client.fail('An unexpected docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc()) + + +if __name__ == '__main__': + main()