docker_image: improve usage (#52894)

* Add source option.

* Split force parameter into force_source, force_absent and force_tag.

* Move all build-related options into a suboption called build.

* Add changelog.
This commit is contained in:
Felix Fontein 2019-03-14 10:46:38 +01:00 committed by John R Barker
parent fa7c8ce0b4
commit 35e7fb776a
5 changed files with 378 additions and 108 deletions

View file

@ -21,41 +21,161 @@ short_description: Manage docker images.
version_added: "1.5"
description:
- Build, load or pull an image, making the image available for creating containers. Also supports tagging an
image into a repository and archiving an image to a .tar file.
- Build, load or pull an image, making the image available for creating containers. Also supports tagging an
image into a repository and archiving an image to a .tar file.
- Since Ansible 2.8, it is recommended to explicitly specify the image's source (C(source=build),
C(source=load), C(source=pull) or C(source=local)). This will be required from Ansible 2.12 on.
options:
source:
description:
- "Determines where the module will try to retrieve the image from."
- "Use C(build) to build the image from a C(Dockerfile). I(path) must
be specified when this value is used."
- "Use C(load) to load the image from a C(.tar) file. I(load_path) must
be specified when this value is used."
- "Use C(pull) to pull the image from a registry."
- "Use C(local) to make sure that the image is already available on the local
docker daemon, i.e. do not try to build, pull or load the image."
- "Before Ansible 2.12, the value of this option will be auto-detected
to be backwards compatible, but a warning will be issued if it is not
explicitly specified. From Ansible 2.12 on, auto-detection will be disabled
and this option will be made mandatory."
type: str
choices:
- build
- load
- pull
- local
version_added: "2.8"
build:
description:
- "Specifies options used for building images."
type: dict
suboptions:
cache_from:
description:
- List of image names to consider as cache source.
type: list
dockerfile:
description:
- Use with state C(present) to provide an alternate name for the Dockerfile to use when building an image.
type: str
http_timeout:
description:
- Timeout for HTTP requests during the image build operation. Provide a positive integer value for the number of
seconds.
type: int
path:
description:
- Use with state 'present' to build an image. Will be the path to a directory containing the context and
Dockerfile for building an image.
type: path
required: yes
pull:
description:
- When building an image downloads any updates to the FROM image in Dockerfile.
type: bool
default: yes
rm:
description:
- Remove intermediate containers after build.
type: bool
default: yes
network:
description:
- The network to use for C(RUN) build instructions.
type: str
nocache:
description:
- Do not use cache when building an image.
type: bool
default: no
args:
description:
- Provide a dictionary of C(key:value) build arguments that map to Dockerfile ARG directive.
- Docker expects the value to be a string. For convenience any non-string values will be converted to strings.
- Requires Docker API >= 1.21.
type: dict
container_limits:
description:
- A dictionary of limits applied to each container created by the build process.
type: dict
suboptions:
memory:
description:
- Set memory limit for build.
type: int
memswap:
description:
- Total memory (memory + swap), -1 to disable swap.
type: int
cpushares:
description:
- CPU shares (relative weight).
type: int
cpusetcpus:
description:
- CPUs in which to allow execution, e.g., "0-3", "0,1".
type: str
version_added: "2.8"
archive_path:
description:
- Use with state C(present) to archive an image to a .tar file.
type: path
version_added: "2.1"
cache_from:
description:
- List of image names to consider as cache source.
type: list
version_added: "2.8"
load_path:
description:
- Use with state C(present) to load an image from a .tar file.
- Set I(source) to C(load) if you want to load the image. The option will
be set automatically before Ansible 2.12 if this option is used (except
if I(path) is specified as well, in which case building will take precedence).
From Ansible 2.12 on, you have to set I(source) to C(load).
type: path
version_added: "2.2"
dockerfile:
description:
- Use with state C(present) to provide an alternate name for the Dockerfile to use when building an image.
- Please use I(build.dockerfile) instead. This option will be removed in Ansible 2.12.
type: str
version_added: "2.0"
force:
description:
- Use with state I(absent) to un-tag and remove all images matching the specified name. Use with state
C(present) to build, load or pull an image when the image already exists.
C(present) to build, load or pull an image when the image already exists. Also use with state C(present)
to force tagging an image.
- Please stop using this option, and use the more specialized force options
I(force_source), I(force_absent) and I(force_tag) instead.
- This option will be removed in Ansible 2.12.
type: bool
default: no
version_added: "2.1"
force_source:
description:
- Use with state C(present) to build, load or pull an image (depending on the
value of the I(source) option) when the image already exists.
type: bool
default: false
version_added: "2.8"
force_absent:
description:
- Use with state I(absent) to un-tag and remove all images matching the specified name.
type: bool
default: false
version_added: "2.8"
force_tag:
description:
- Use with state C(present) to force tagging an image.
- Please stop using this option, and use the more specialized force options
I(force_source), I(force_absent) and I(force_tag) instead.
- This option will be removed in Ansible 2.12.
type: bool
default: false
version_added: "2.8"
http_timeout:
description:
- Timeout for HTTP requests during the image build operation. Provide a positive integer value for the number of
seconds.
- Please use I(build.http_timeout) instead. This option will be removed in Ansible 2.12.
type: int
version_added: "2.1"
name:
@ -69,12 +189,17 @@ options:
description:
- Use with state 'present' to build an image. Will be the path to a directory containing the context and
Dockerfile for building an image.
- Set I(source) to C(build) if you want to build the image. The option will
be set automatically before Ansible 2.12 if this option is used. From Ansible 2.12
on, you have to set I(source) to C(build).
- Please use I(build.path) instead. This option will be removed in Ansible 2.12.
type: path
aliases:
- build_path
pull:
description:
- When building an image downloads any updates to the FROM image in Dockerfile.
- Please use I(build.pull) instead. This option will be removed in Ansible 2.12.
type: bool
default: yes
version_added: "2.1"
@ -87,17 +212,14 @@ options:
rm:
description:
- Remove intermediate containers after build.
- Please use I(build.rm) instead. This option will be removed in Ansible 2.12.
type: bool
default: yes
version_added: "2.1"
network:
description:
- The network to use for C(RUN) build instructions.
type: str
version_added: "2.8"
nocache:
description:
- Do not use cache when building an image.
- Please use I(build.nocache) instead. This option will be removed in Ansible 2.12.
type: bool
default: no
repository:
@ -136,11 +258,13 @@ options:
- Provide a dictionary of C(key:value) build arguments that map to Dockerfile ARG directive.
- Docker expects the value to be a string. For convenience any non-string values will be converted to strings.
- Requires Docker API >= 1.21.
- Please use I(build.args) instead. This option will be removed in Ansible 2.12.
type: dict
version_added: "2.2"
container_limits:
description:
- A dictionary of limits applied to each container created by the build process.
- Please use I(build.container_limits) instead. This option will be removed in Ansible 2.12.
type: dict
suboptions:
memory:
@ -194,26 +318,30 @@ EXAMPLES = '''
- name: pull an image
docker_image:
name: pacur/centos-7
source: pull
- name: Tag and push to docker hub
docker_image:
name: pacur/centos-7:56
repository: dcoppenhagan/myimage:7.56
push: yes
source: local
- name: Tag and push to local registry
docker_image:
# Image will be centos:7
name: centos
# Will be pushed to localhost:5000/centos:7
repository: localhost:5000/centos
tag: 7
push: yes
# Image will be centos:7
name: centos
# Will be pushed to localhost:5000/centos:7
repository: localhost:5000/centos
tag: 7
push: yes
source: local
- name: Add tag latest to image
docker_image:
name: myimage:7.1.2
repository: myimage:latest
source: local
- name: Remove image
docker_image:
@ -223,16 +351,19 @@ EXAMPLES = '''
- name: Build an image and push it to a private repo
docker_image:
path: ./sinatra
build:
path: ./sinatra
name: registry.ansible.com/chouseknecht/sinatra
tag: v1
push: yes
source: build
- name: Archive image
docker_image:
name: registry.ansible.com/chouseknecht/sinatra
tag: v1
archive_path: my_sinatra.tar
source: local
- name: Load image from archive and push to a private registry
docker_image:
@ -240,23 +371,28 @@ EXAMPLES = '''
tag: v1
push: yes
load_path: my_sinatra.tar
source: load
- name: Build image and with buildargs
- name: Build image and with build args
docker_image:
path: /path/to/build/dir
name: myimage
buildargs:
log_volume: /var/log/myapp
listen_port: 8080
name: myimage
build:
path: /path/to/build/dir
args:
log_volume: /var/log/myapp
listen_port: 8080
source: build
- name: Build image using cache source
docker_image:
name: myimage:latest
path: /path/to/build/dir
# Use as cache source for building myimage
cache_from:
- nginx:latest
- alpine:3.8
build:
path: /path/to/build/dir
# Use as cache source for building myimage
cache_from:
- nginx:latest
- alpine:3.8
source: build
'''
RETURN = '''
@ -296,24 +432,28 @@ class ImageManager(DockerBaseClass):
parameters = self.client.module.params
self.check_mode = self.client.check_mode
self.source = parameters['source']
build = parameters['build'] or dict()
self.archive_path = parameters.get('archive_path')
self.cache_from = parameters.get('cache_from')
self.container_limits = parameters.get('container_limits')
self.dockerfile = parameters.get('dockerfile')
self.force = parameters.get('force')
self.cache_from = build.get('cache_from')
self.container_limits = build.get('container_limits')
self.dockerfile = build.get('dockerfile')
self.force_source = parameters.get('force_source')
self.force_absent = parameters.get('force_absent')
self.force_tag = parameters.get('force_tag')
self.load_path = parameters.get('load_path')
self.name = parameters.get('name')
self.network = parameters.get('network')
self.nocache = parameters.get('nocache')
self.path = parameters.get('path')
self.pull = parameters.get('pull')
self.network = build.get('network')
self.nocache = build.get('nocache')
self.build_path = build.get('path')
self.pull = build.get('pull')
self.repository = parameters.get('repository')
self.rm = parameters.get('rm')
self.rm = build.get('rm')
self.state = parameters.get('state')
self.tag = parameters.get('tag')
self.http_timeout = parameters.get('http_timeout')
self.http_timeout = build.get('http_timeout')
self.push = parameters.get('push')
self.buildargs = parameters.get('buildargs')
self.buildargs = build.get('args')
# If name contains a tag, it takes precedence over tag parameter.
if not is_image_name_id(self.name):
@ -322,7 +462,7 @@ class ImageManager(DockerBaseClass):
self.name = repo
self.tag = repo_tag
if self.state in ['present', 'build']:
if self.state == 'present':
self.present()
elif self.state == 'absent':
self.absent()
@ -339,20 +479,20 @@ class ImageManager(DockerBaseClass):
'''
image = self.client.find_image(name=self.name, tag=self.tag)
if not image or self.force:
if self.path:
if not image or self.force_source:
if self.source == 'build':
# Build the image
if not os.path.isdir(self.path):
self.fail("Requested build path %s could not be found or you do not have access." % self.path)
if not os.path.isdir(self.build_path):
self.fail("Requested build path %s could not be found or you do not have access." % self.build_path)
image_name = self.name
if self.tag:
image_name = "%s:%s" % (self.name, self.tag)
self.log("Building image %s" % image_name)
self.results['actions'].append("Built image %s from %s" % (image_name, self.path))
self.results['actions'].append("Built image %s from %s" % (image_name, self.build_path))
self.results['changed'] = True
if not self.check_mode:
self.results['image'] = self.build_image()
elif self.load_path:
elif self.source == 'load':
# Load the image from an archive
if not os.path.isfile(self.load_path):
self.fail("Error loading image %s. Specified path %s does not exist." % (self.name,
@ -364,12 +504,18 @@ class ImageManager(DockerBaseClass):
self.results['changed'] = True
if not self.check_mode:
self.results['image'] = self.load_image()
else:
elif self.source == 'pull':
# pull the image
self.results['actions'].append('Pulled image %s:%s' % (self.name, self.tag))
self.results['changed'] = True
if not self.check_mode:
self.results['image'], dummy = self.client.pull_image(self.name, tag=self.tag)
elif self.source == 'local':
if image is None:
name = self.name
if self.tag:
name = "%s:%s" % (self.name, self.tag)
self.client.fail('Cannot find the image %s locally.' % name)
if not self.check_mode and image and image['Id'] == self.results['image']['Id']:
self.results['changed'] = False
@ -379,7 +525,7 @@ class ImageManager(DockerBaseClass):
if self.push and not self.repository:
self.push_image(self.name, self.tag)
elif self.repository:
self.tag_image(self.name, self.tag, self.repository, force=self.force, push=self.push)
self.tag_image(self.name, self.tag, self.repository, push=self.push)
def absent(self):
'''
@ -397,7 +543,7 @@ class ImageManager(DockerBaseClass):
if image:
if not self.check_mode:
try:
self.client.remove_image(name, force=self.force)
self.client.remove_image(name, force=self.force_absent)
except Exception as exc:
self.fail("Error removing image %s - %s" % (name, str(exc)))
@ -491,14 +637,13 @@ class ImageManager(DockerBaseClass):
self.results['image'] = dict()
self.results['image']['push_status'] = status
def tag_image(self, name, tag, repository, force=False, push=False):
def tag_image(self, name, tag, repository, push=False):
'''
Tag an image into a repository.
:param name: name of the image. required.
:param tag: image tag.
:param repository: path to the repository. required.
:param force: bool. force tagging, even it image already exists with the repository path.
:param push: bool. push the image once it's tagged.
:return: None
'''
@ -511,7 +656,7 @@ class ImageManager(DockerBaseClass):
found = 'found' if image else 'not found'
self.log("image %s was %s" % (repo, found))
if not image or force:
if not image or self.force_tag:
self.log("tagging %s:%s to %s:%s" % (name, tag, repo, repo_tag))
self.results['changed'] = True
self.results['actions'].append("Tagged image %s:%s to %s:%s" % (name, tag, repo, repo_tag))
@ -541,7 +686,7 @@ class ImageManager(DockerBaseClass):
:return: image dict
'''
params = dict(
path=self.path,
path=self.build_path,
tag=self.name,
rm=self.rm,
nocache=self.nocache,
@ -614,39 +759,70 @@ class ImageManager(DockerBaseClass):
def main():
argument_spec = dict(
source=dict(type='str', choices=['build', 'load', 'pull', 'local']),
build=dict(type='dict', suboptions=dict(
cache_from=dict(type='list', elements='str'),
container_limits=dict(type='dict', options=dict(
memory=dict(type='int'),
memswap=dict(type='int'),
cpushares=dict(type='int'),
cpusetcpus=dict(type='str'),
)),
dockerfile=dict(type='str'),
http_timeout=dict(type='int'),
network=dict(type='str'),
nocache=dict(type='bool', default=False),
path=dict(type='path', required=True),
pull=dict(type='bool', default=True),
rm=dict(type='bool', default=True),
args=dict(type='dict'),
)),
archive_path=dict(type='path'),
cache_from=dict(type='list', elements='str'),
container_limits=dict(type='dict', options=dict(
memory=dict(type='int'),
memswap=dict(type='int'),
cpushares=dict(type='int'),
cpusetcpus=dict(type='str'),
)),
dockerfile=dict(type='str'),
force=dict(type='bool', default=False),
http_timeout=dict(type='int'),
), removedin_version='2.12'),
dockerfile=dict(type='str', removedin_version='2.12'),
force=dict(type='bool', removed_in_version='2.12'),
force_source=dict(type='bool', default=False),
force_absent=dict(type='bool', default=False),
force_tag=dict(type='bool', default=False),
http_timeout=dict(type='int', removedin_version='2.12'),
load_path=dict(type='path'),
name=dict(type='str', required=True),
network=dict(type='str'),
nocache=dict(type='bool', default=False),
path=dict(type='path', aliases=['build_path']),
pull=dict(type='bool', default=True),
nocache=dict(type='bool', default=False, removedin_version='2.12'),
path=dict(type='path', aliases=['build_path'], removedin_version='2.12'),
pull=dict(type='bool', default=True, removedin_version='2.12'),
push=dict(type='bool', default=False),
repository=dict(type='str'),
rm=dict(type='bool', default=True),
rm=dict(type='bool', default=True, removedin_version='2.12'),
state=dict(type='str', default='present', choices=['absent', 'present', 'build']),
tag=dict(type='str', default='latest'),
use_tls=dict(type='str', choices=['no', 'encrypt', 'verify'], removed_in_version='2.11'),
buildargs=dict(type='dict'),
buildargs=dict(type='dict', removedin_version='2.12'),
)
option_minimal_versions = dict(
cache_from=dict(docker_py_version='2.1.0', docker_api_version='1.25'),
network=dict(docker_py_version='2.4.0', docker_api_version='1.25'),
)
required_if = [
# ('state', 'present', ['source']), -- enable in Ansible 2.12.
# ('source', 'build', ['build']), -- enable in Ansible 2.12.
('source', 'load', ['load_path']),
]
def detect_build_cache_from(client):
return bool(client.params['build'] and client.params['build']['cache_from'] is not None)
def detect_build_network(client):
return bool(client.params['build'] and client.params['build']['network'] 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)
client = AnsibleDockerClient(
argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True,
min_docker_version='1.8.0',
min_docker_api_version='1.20',
@ -657,11 +833,60 @@ def main():
client.module.warn('The "build" state has been deprecated for a long time '
'and will be removed in Ansible 2.11. Please use '
'"present", which has the same meaning as "build".')
client.module.params['state'] = 'present'
if client.module.params['use_tls']:
client.module.warn('The "use_tls" option has been deprecated for a long time '
'and will be removed in Ansible 2.11. Please use the'
'"tls" and "tls_verify" options instead.')
build_options = dict(
container_limits='container_limits',
dockerfile='dockerfile',
http_timeout='http_timeout',
nocache='nocache',
path='path',
pull='pull',
rm='rm',
buildargs='args',
)
for option, build_option in build_options.items():
default_value = None
if option in ('pull', 'rm'):
default_value = True
elif option in ('nocache', ):
default_value = False
if client.module.params[option] != default_value:
if client.module.params['build'] is None:
client.module.params['build'] = dict()
if client.module.params['build'].get(build_option) != default_value:
client.fail('Cannot specify both %s and build.%s!' % (option, build_option))
client.module.params['build'][build_option] = client.module.params[option]
client.module.warn('Please specify build.%s instead of %s. The %s option '
'has been renamed and will be removed in Ansible 2.12.' % (build_option, option, option))
if client.module.params['source'] == 'build' and \
(not client.module.params['build'] or not client.module.params['build'].get('path')):
client.module.fail('If "source" is set to "build", the "build.path" option must be specified.')
if client.module.params['state'] == 'present' and client.module.params['source'] is None:
# Autodetection. To be removed in Ansible 2.12.
if (client.module.params['build'] or dict()).get('path'):
client.module.params['source'] = 'build'
elif client.module.params['load_path']:
client.module.params['source'] = 'load'
else:
client.module.params['source'] = 'pull'
client.module.warn('The value of the "source" option was determined to be "%s". '
'Please set the "source" option explicitly. Autodetection will '
'be removed in Ansible 2.12.' % client.module.params['source'])
if client.module.params['force']:
client.module.params['force_source'] = True
client.module.params['force_absent'] = True
client.module.params['force_tag'] = True
client.module.warn('The "force" option will be removed in Ansible 2.12. Please '
'use the "force_source", "force_absent" or "force_tag" option '
'instead, depending on what you want to force.')
results = dict(
changed=False,
actions=[],