diff --git a/changelogs/fragments/58487-docker_image_build.target_option.yaml b/changelogs/fragments/58487-docker_image_build.target_option.yaml new file mode 100644 index 00000000000..c636ff5047e --- /dev/null +++ b/changelogs/fragments/58487-docker_image_build.target_option.yaml @@ -0,0 +1,2 @@ +minor_changes: +- "docker_image - Add ``build.target`` option." diff --git a/lib/ansible/modules/cloud/docker/docker_image.py b/lib/ansible/modules/cloud/docker/docker_image.py index b87f14a1178..6a9a672c46d 100644 --- a/lib/ansible/modules/cloud/docker/docker_image.py +++ b/lib/ansible/modules/cloud/docker/docker_image.py @@ -126,6 +126,12 @@ options: be set in the container being built. - Needs Docker SDK for Python >= 3.7.0. type: bool + target: + description: + - When building an image specifies an intermediate build stage by + name as a final stage for the resulting image. + type: str + version_added: "2.9" version_added: "2.8" archive_path: description: @@ -461,6 +467,7 @@ class ImageManager(DockerBaseClass): self.nocache = build.get('nocache', False) self.build_path = build.get('path') self.pull = build.get('pull') + self.target = build.get('target') self.repository = parameters.get('repository') self.rm = build.get('rm', True) self.state = parameters.get('state') @@ -732,6 +739,8 @@ class ImageManager(DockerBaseClass): # use_config_proxy is True and buildargs is None if 'buildargs' not in params: params['buildargs'] = {} + if self.target: + params['target'] = self.target for line in self.client.build(**params): # line = json.loads(line) @@ -798,6 +807,7 @@ def main(): rm=dict(type='bool', default=True), args=dict(type='dict'), use_config_proxy=dict(type='bool'), + target=dict(type='str'), )), archive_path=dict(type='path'), container_limits=dict(type='dict', options=dict( @@ -838,12 +848,16 @@ def main(): def detect_build_network(client): return client.module.params['build'] and client.module.params['build'].get('network') is not None + def detect_build_target(client): + return client.module.params['build'] and client.module.params['build'].get('target') is not None + def detect_use_config_proxy(client): return client.module.params['build'] and client.module.params['build'].get('use_config_proxy') is not None option_minimal_versions = dict() option_minimal_versions["build.cache_from"] = dict(docker_py_version='2.1.0', docker_api_version='1.25', detect_usage=detect_build_cache_from) option_minimal_versions["build.network"] = dict(docker_py_version='2.4.0', docker_api_version='1.25', detect_usage=detect_build_network) + option_minimal_versions["build.target"] = dict(docker_py_version='2.4.0', detect_usage=detect_build_target) option_minimal_versions["build.use_config_proxy"] = dict(docker_py_version='3.7.0', detect_usage=detect_use_config_proxy) client = AnsibleDockerClient( diff --git a/test/integration/targets/docker_image/files/StagedDockerfile b/test/integration/targets/docker_image/files/StagedDockerfile new file mode 100644 index 00000000000..ebc4df42f04 --- /dev/null +++ b/test/integration/targets/docker_image/files/StagedDockerfile @@ -0,0 +1,7 @@ +FROM busybox AS first +ENV dir /first +WORKDIR ${dir} + +FROM busybox AS second +ENV dir /second +WORKDIR ${dir} diff --git a/test/integration/targets/docker_image/tasks/tests/options.yml b/test/integration/targets/docker_image/tasks/tests/options.yml index 5ee1a19a55c..6a7ea3d4b0e 100644 --- a/test/integration/targets/docker_image/tasks/tests/options.yml +++ b/test/integration/targets/docker_image/tasks/tests/options.yml @@ -290,3 +290,29 @@ that: - path_1 is changed - path_2 is not changed + +#################################################################### +## target ########################################################## +#################################################################### + +- name: Build multi-stage image + docker_image: + name: "{{ iname }}" + build: + path: "{{ role_path }}/files" + dockerfile: "StagedDockerfile" + target: first + pull: no + source: build + register: dockerfile_2 + +- name: cleanup + docker_image: + name: "{{ iname }}" + state: absent + force_absent: yes + +- assert: + that: + - dockerfile_2 is changed + - dockerfile_2.image.Config.WorkingDir == '/first'