#!/usr/bin/python
# Copyright (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = r'''
  module: podman_image
  author:
      - Sam Doran (@samdoran)
  short_description: Pull images for use by podman
  notes: []
  description:
      - Build, pull, or push images using Podman.
  options:
    arch:
      description:
        - CPU architecture for the container image
      type: str
    name:
      description:
        - Name of the image to pull, push, or delete. It may contain a tag using the format C(image:tag).
      required: True
      type: str
    executable:
      description:
        - Path to C(podman) executable if it is not in the C($PATH) on the machine running C(podman).
      default: 'podman'
      type: str
    ca_cert_dir:
      description:
        - Path to directory containing TLS certificates and keys to use.
      type: 'path'
    tag:
      description:
        - Tag of the image to pull, push, or delete.
      default: "latest"
      type: str
    pull:
      description: Whether or not to pull the image.
      default: True
      type: bool
    pull_extra_args:
      description:
        - Extra arguments to pass to the pull command.
      type: str
    push:
      description: Whether or not to push an image.
      default: False
      type: bool
    path:
      description: Path to the build context directory.
      type: str
    force:
      description:
        - Whether or not to force push or pull an image.
        - When building, force the build even if the image already exists.
      type: bool
      default: False
    state:
      description:
        - Whether an image should be present, absent, or built.
      default: "present"
      type: str
      choices:
        - present
        - absent
        - build
        - quadlet
    validate_certs:
      description:
        - Require HTTPS and validate certificates when pulling or pushing.
          Also used during build if a pull or push is necessary.
      type: bool
      aliases:
        - tlsverify
        - tls_verify
    password:
      description:
        - Password to use when authenticating to remote registries.
      type: str
    username:
      description:
        - username to use when authenticating to remote registries.
      type: str
    auth_file:
      description:
        - Path to file containing authorization credentials to the remote registry.
      aliases:
        - authfile
      type: path
    build:
      description: Arguments that control image build.
      type: dict
      default: {}
      aliases:
        - build_args
        - buildargs
      suboptions:
        container_file:
          description:
            - Content of the Containerfile to use for building the image.
              Mutually exclusive with the C(file) option which is path to the existing Containerfile.
          type: str
        file:
          description:
            - Path to the Containerfile if it is not in the build context directory.
              Mutually exclusive with the C(container_file) option.
          type: path
        volume:
          description:
            - Specify multiple volume / mount options to mount one or more mounts to a container.
          type: list
          elements: str
        annotation:
          description:
            - Dictionary of key=value pairs to add to the image. Only works with OCI images.
              Ignored for Docker containers.
          type: dict
        force_rm:
          description:
            - Always remove intermediate containers after a build, even if the build is unsuccessful.
          type: bool
          default: False
        format:
          description:
            - Format of the built image.
          type: str
          choices:
            - docker
            - oci
          default: "oci"
        cache:
          description:
            - Whether or not to use cached layers when building an image
          type: bool
          default: True
        rm:
          description: Remove intermediate containers after a successful build
          type: bool
          default: True
        extra_args:
          description:
            - Extra args to pass to build, if executed. Does not idempotently check for new build args.
          type: str
        target:
          description:
            - Specify the target build stage to build.
          type: str
    push_args:
      description: Arguments that control pushing images.
      type: dict
      default: {}
      suboptions:
        compress:
          description:
            - Compress tarball image layers when pushing to a directory using the 'dir' transport.
          type: bool
        format:
          description:
            - Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source)
          type: str
          choices:
            - oci
            - v2s1
            - v2s2
        remove_signatures:
          description: Discard any pre-existing signatures in the image
          type: bool
        sign_by:
          description:
            - Path to a key file to use to sign the image.
          type: str
        dest:
          description: Path or URL where image will be pushed.
          type: str
          aliases:
            - destination
        transport:
          description:
            - Transport to use when pushing in image. If no transport is set, will attempt to push to a remote registry
          type: str
          choices:
            - dir
            - docker
            - docker-archive
            - docker-daemon
            - oci-archive
            - ostree
        extra_args:
          description:
            - Extra args to pass to push, if executed. Does not idempotently check for new push args.
          type: str
    quadlet_dir:
      description:
        - Path to the directory to write quadlet file in.
          By default, it will be set as C(/etc/containers/systemd/) for root user,
          C(~/.config/containers/systemd/) for non-root users.
      type: path
      required: false
    quadlet_filename:
      description:
        - Name of quadlet file to write. By default it takes image name without prefixes and tags.
      type: str
    quadlet_file_mode:
      description:
        - The permissions of the quadlet file.
        - The O(quadlet_file_mode) can be specied as octal numbers or as a symbolic mode (for example, V(u+rwx) or V(u=rw,g=r,o=r)).
          For octal numbers format, you must either add a leading zero so that Ansible's YAML parser knows it is an
          octal number (like V(0644) or V(01777)) or quote it (like V('644') or V('1777')) so Ansible receives a string
          and can do its own conversion from string into number. Giving Ansible a number without following one of these
          rules will end up with a decimal number which will have unexpected results.
        - If O(quadlet_file_mode) is not specified and the quadlet file B(does not) exist, the default V('0640') mask will be used
          when setting the mode for the newly created file.
        - If O(quadlet_file_mode) is not specified and the quadlet file B(does) exist, the mode of the existing file will be used.
        - Specifying O(quadlet_file_mode) is the best way to ensure files are created with the correct permissions.
      type: raw
      required: false
    quadlet_options:
      description:
        - Options for the quadlet file. Provide missing in usual network args
          options as a list of lines to add.
      type: list
      elements: str
      required: false
'''

EXAMPLES = r"""
- name: Pull an image
  containers.podman.podman_image:
    name: quay.io/bitnami/wildfly

- name: Remove an image
  containers.podman.podman_image:
    name: quay.io/bitnami/wildfly
    state: absent

- name: Remove an image with image id
  containers.podman.podman_image:
    name: 0e901e68141f
    state: absent

- name: Pull a specific version of an image
  containers.podman.podman_image:
    name: redis
    tag: 4

- name: Build a basic OCI image
  containers.podman.podman_image:
    name: nginx
    path: /path/to/build/dir

- name: Build a basic OCI image with advanced parameters
  containers.podman.podman_image:
    name: nginx
    path: /path/to/build/dir
    build:
      cache: no
      force_rm: true
      format: oci
      annotation:
        app: nginx
        function: proxy
        info: Load balancer for my cool app
      extra_args: "--build-arg KEY=value"

- name: Build a Docker formatted image
  containers.podman.podman_image:
    name: nginx
    path: /path/to/build/dir
    build:
      format: docker

- name: Build and push an image using existing credentials
  containers.podman.podman_image:
    name: nginx
    path: /path/to/build/dir
    push: true
    push_args:
      dest: quay.io/acme

- name: Build and push an image using an auth file
  containers.podman.podman_image:
    name: nginx
    push: true
    auth_file: /etc/containers/auth.json
    push_args:
      dest: quay.io/acme

- name: Build and push an image using username and password
  containers.podman.podman_image:
    name: nginx
    push: true
    username: bugs
    password: "{{ vault_registry_password }}"
    push_args:
      dest: quay.io/acme

- name: Build and push an image to multiple registries
  containers.podman.podman_image:
    name: "{{ item }}"
    path: /path/to/build/dir
    push: true
    auth_file: /etc/containers/auth.json
  loop:
    - quay.io/acme/nginx
    - docker.io/acme/nginx

- name: Build and push an image to multiple registries with separate parameters
  containers.podman.podman_image:
    name: "{{ item.name }}"
    tag: "{{ item.tag }}"
    path: /path/to/build/dir
    push: true
    auth_file: /etc/containers/auth.json
    push_args:
      dest: "{{ item.dest }}"
  loop:
    - name: nginx
      tag: 4
      dest: docker.io/acme

    - name: nginx
      tag: 3
      dest: docker.io/acme

- name: Pull an image for a specific CPU architecture
  containers.podman.podman_image:
    name: nginx
    arch: amd64

- name: Build a container from file inline
  containers.podman.podman_image:
    name: mycustom_image
    state: build
    build:
      container_file: |-
        FROM alpine:latest
        CMD echo "Hello, World!"

- name: Create a quadlet file for an image
  containers.podman.podman_image:
    name: docker.io/library/alpine:latest
    state: quadlet
    quadlet_dir: /etc/containers/systemd
    quadlet_filename: alpine-latest
    quadlet_file_mode: '0640'
    quadlet_options:
      - Variant=arm/v7
      - |
        [Install]
        WantedBy=default.target
"""

RETURN = r"""
  image:
    description:
      - Image inspection results for the image that was pulled, pushed, or built.
    returned: success
    type: dict
    sample: [
                {
                    "Id": "6d1ef012b5674ad8a127ecfa9b5e6f5178d171b90ee462846974177fd9bdd39f",
                    "Digest": "sha256:8421d9a84432575381bfabd248f1eb56f3aa21d9d7cd2511583c68c9b7511d10",
                    "RepoTags": [
                        "docker.io/library/alpine:3.7"
                    ],
                    "RepoDigests": [
                        "docker.io/library/alpine@sha256:8421...",
                        "docker.io/library/alpine@sha256:9225..."
                    ],
                    "Parent": "",
                    "Comment": "",
                    "Created": "2019-03-07T22:19:53.447205048Z",
                    "Config": {
                        "Env": [
                                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
                        ],
                        "Cmd": [
                                "/bin/sh"
                        ],
                        "ArgsEscaped": true
                    },
                    "Version": "18.06.1-ce",
                    "Author": "",
                    "Architecture": "amd64",
                    "Os": "linux",
                    "Size": 4467084,
                    "VirtualSize": 4467084,
                    "GraphDriver": {
                        "Name": "overlay",
                        "Data": {
                                "UpperDir": "/home/user/.local/share/containers/storage/overlay/3fc6.../diff",
                                "WorkDir": "/home/user/.local/share/containers/storage/overlay/3fc6.../work"
                        }
                    },
                    "RootFS": {
                        "Type": "layers",
                        "Layers": [
                                "sha256:3fc6..."
                        ]
                    },
                    "Labels": null,
                    "Annotations": {},
                    "ManifestType": "application/vnd.docker.distribution.manifest.v2+json",
                    "User": "",
                    "History": [
                        {
                                "created": "2019-03-07T22:19:53.313789681Z",
                                "created_by": "/bin/sh -c #(nop) ADD file:aa17928... in / "
                        },
                        {
                                "created": "2019-03-07T22:19:53.447205048Z",
                                "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",
                                "empty_layer": true
                        }
                    ],
                    "NamesHistory": [
                        "docker.io/library/alpine:3.7"
                    ]
                }
            ]

"""

import json  # noqa: E402
import os  # noqa: E402
import re  # noqa: E402
import shlex  # noqa: E402
import tempfile  # noqa: E402
import time  # noqa: E402
import hashlib  # noqa: E402
import sys  # noqa: E402

from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import run_podman_command
from ansible_collections.containers.podman.plugins.module_utils.podman.quadlet import create_quadlet_state


class PodmanImageManager(object):

    def __init__(self, module, results):

        super(PodmanImageManager, self).__init__()

        self.module = module
        self.results = results
        self.name = self.module.params.get('name')
        self.executable = self.module.get_bin_path(module.params.get('executable'), required=True)
        self.tag = self.module.params.get('tag')
        self.pull = self.module.params.get('pull')
        self.pull_extra_args = self.module.params.get('pull_extra_args')
        self.push = self.module.params.get('push')
        self.path = self.module.params.get('path')
        self.force = self.module.params.get('force')
        self.state = self.module.params.get('state')
        self.validate_certs = self.module.params.get('validate_certs')
        self.auth_file = self.module.params.get('auth_file')
        self.username = self.module.params.get('username')
        self.password = self.module.params.get('password')
        self.ca_cert_dir = self.module.params.get('ca_cert_dir')
        self.build = self.module.params.get('build')
        self.push_args = self.module.params.get('push_args')
        self.arch = self.module.params.get('arch')

        repo, repo_tag = parse_repository_tag(self.name)
        if repo_tag:
            self.name = repo
            self.tag = repo_tag

        delimiter = ':' if "sha256" not in self.tag else '@'
        self.image_name = '{name}{d}{tag}'.format(name=self.name, d=delimiter, tag=self.tag)

        if self.state in ['present', 'build']:
            self.present()

        if self.state in ['absent']:
            self.absent()

        if self.state == 'quadlet':
            self.make_quadlet()

    def _run(self, args, expected_rc=0, ignore_errors=False):
        cmd = " ".join([self.executable]
                       + [to_native(i) for i in args])
        self.module.log("PODMAN-IMAGE-DEBUG: %s" % cmd)
        self.results['podman_actions'].append(cmd)
        return run_podman_command(
            module=self.module,
            executable=self.executable,
            args=args,
            expected_rc=expected_rc,
            ignore_errors=ignore_errors)

    def _get_id_from_output(self, lines, startswith=None, contains=None, split_on=' ', maxsplit=1):
        layer_ids = []
        for line in lines.splitlines():
            if startswith and line.startswith(startswith) or contains and contains in line:
                splitline = line.rsplit(split_on, maxsplit)
                layer_ids.append(splitline[1])

        # Podman 1.4 changed the output to only include the layer id when run in quiet mode
        if not layer_ids:
            layer_ids = lines.splitlines()

        return layer_ids[-1]

    def _find_containerfile_from_context(self):
        """
        Find a Containerfile/Dockerfile path inside a podman build context.
        Return 'None' if none exist.
        """

        containerfile_path = None
        for filename in [os.path.join(self.path, fname) for fname in ["Containerfile", "Dockerfile"]]:
            if os.path.exists(filename):
                containerfile_path = filename
                break
        return containerfile_path

    def _get_containerfile_contents(self):
        """
        Get the path to the Containerfile for an invocation
        of the module, and return its contents.

        See if either `file` or `container_file` in build args are populated,
        fetch their contents if so. If not, return the contents of the Containerfile
        or Dockerfile from inside the build context, if present.

        If we don't find a Containerfile/Dockerfile in any of the above
        locations, return 'None'.
        """

        build_file_arg = self.build.get('file') if self.build else None
        containerfile_contents = self.build.get('container_file') if self.build else None

        container_filename = None
        if build_file_arg:
            container_filename = build_file_arg
        elif self.path and not build_file_arg:
            container_filename = self._find_containerfile_from_context()

        if not containerfile_contents and os.access(container_filename, os.R_OK):
            with open(container_filename) as f:
                containerfile_contents = f.read()

        return containerfile_contents

    def _hash_containerfile_contents(self, containerfile_contents):
        """
        When given the contents of a Containerfile/Dockerfile,
        return a sha256 hash of these contents.
        """
        if not containerfile_contents:
            return None

        # usedforsecurity keyword arg was introduced in python 3.9
        if sys.version_info < (3, 9):
            return hashlib.sha256(
                containerfile_contents.encode(),
            ).hexdigest()
        else:
            return hashlib.sha256(
                containerfile_contents.encode(),
                usedforsecurity=False
            ).hexdigest()

    def _get_args_containerfile_hash(self):
        """
        If we can find a Containerfile in any of the module args
        or inside the build context, hash its contents.

        If we don't have this, return an empty string.
        """

        args_containerfile_hash = None

        context_has_containerfile = self.path and self._find_containerfile_from_context()

        should_hash_args_containerfile = (
            context_has_containerfile or
            self.build.get('file') is not None or
            self.build.get('container_file') is not None
        )

        if should_hash_args_containerfile:
            args_containerfile_hash = self._hash_containerfile_contents(
                self._get_containerfile_contents()
            )
        return args_containerfile_hash

    def present(self):
        image = self.find_image()

        existing_image_containerfile_hash = ""
        args_containerfile_hash = self._get_args_containerfile_hash()

        if image:
            digest_before = image[0].get('Digest', image[0].get('digest'))
            labels = image[0].get('Labels') or {}
            if "containerfile.hash" in labels:
                existing_image_containerfile_hash = labels["containerfile.hash"]
        else:
            digest_before = None

        both_hashes_exist_and_differ = (args_containerfile_hash and existing_image_containerfile_hash and
                                        args_containerfile_hash != existing_image_containerfile_hash
                                        )

        if not image or self.force or both_hashes_exist_and_differ:
            if self.state == 'build' or self.path:
                # Build the image
                build_file = self.build.get('file') if self.build else None
                container_file_txt = self.build.get('container_file') if self.build else None
                if build_file and container_file_txt:
                    self.module.fail_json(msg='Cannot specify both build file and container file content!')
                if not self.path and build_file:
                    self.path = os.path.dirname(build_file)
                elif not self.path and not build_file and not container_file_txt:
                    self.module.fail_json(msg='Path to build context or file is required when building an image')
                self.results['actions'].append('Built image {image_name} from {path}'.format(
                    image_name=self.image_name, path=self.path or 'default context'))
                if not self.module.check_mode:
                    self.results['image'], self.results['stdout'] = self.build_image(args_containerfile_hash)
                    image = self.results['image']
            else:
                # Pull the image
                self.results['actions'].append('Pulled image {image_name}'.format(image_name=self.image_name))
                if not self.module.check_mode:
                    image = self.results['image'] = self.pull_image()

            if not image:
                image = self.find_image()
            if not self.module.check_mode:
                digest_after = image[0].get('Digest', image[0].get('digest'))
                self.results['changed'] = digest_before != digest_after
            else:
                self.results['changed'] = True

        if self.push:
            self.results['image'], output = self.push_image()
            self.results['stdout'] += "\n" + output
        if image and not self.results.get('image'):
            self.results['image'] = image

    def absent(self):
        image = self.find_image()
        image_id = self.find_image_id()

        if image:
            self.results['actions'].append('Removed image {name}'.format(name=self.name))
            self.results['changed'] = True
            self.results['image']['state'] = 'Deleted'
            if not self.module.check_mode:
                self.remove_image()
        elif image_id:
            self.results['actions'].append(
                'Removed image with id {id}'.format(id=self.image_name))
            self.results['changed'] = True
            self.results['image']['state'] = 'Deleted'
            if not self.module.check_mode:
                self.remove_image_id()

    def make_quadlet(self):
        results_update = create_quadlet_state(self.module, "image")
        self.results.update(results_update)
        self.module.exit_json(**self.results)

    def find_image(self, image_name=None):
        if image_name is None:
            image_name = self.image_name
        # Let's find out if image exists
        rc, out, err = self._run(['image', 'exists', image_name], ignore_errors=True)
        if rc == 0:
            inspect_json = self.inspect_image(image_name)
        else:
            return None
        args = ['image', 'ls', image_name, '--format', 'json']
        rc, images, err = self._run(args, ignore_errors=True)
        try:
            images = json.loads(images)
        except json.decoder.JSONDecodeError:
            self.module.fail_json(msg='Failed to parse JSON output from podman image ls: {out}'.format(out=images))
        if len(images) == 0:
            return None
        inspect_json = self.inspect_image(image_name)
        if self._is_target_arch(inspect_json, self.arch) or not self.arch:
            return images or inspect_json
        return None

    def _is_target_arch(self, inspect_json=None, arch=None):
        return arch and inspect_json[0]['Architecture'] == arch

    def find_image_id(self, image_id=None):
        if image_id is None:
            # If image id is set as image_name, remove tag
            image_id = re.sub(':.*$', '', self.image_name)
        args = ['image', 'ls', '--quiet', '--no-trunc']
        rc, candidates, err = self._run(args, ignore_errors=True)
        candidates = [re.sub('^sha256:', '', c)
                      for c in str.splitlines(candidates)]
        for c in candidates:
            if c.startswith(image_id):
                return image_id
        return None

    def inspect_image(self, image_name=None):
        if image_name is None:
            image_name = self.image_name
        args = ['inspect', image_name, '--format', 'json']
        rc, image_data, err = self._run(args)
        try:
            image_data = json.loads(image_data)
        except json.decoder.JSONDecodeError:
            self.module.fail_json(msg='Failed to parse JSON output from podman inspect: {out}'.format(out=image_data))
        if len(image_data) > 0:
            return image_data
        else:
            return None

    def pull_image(self, image_name=None):
        if image_name is None:
            image_name = self.image_name

        args = ['pull', image_name, '-q']

        if self.arch:
            args.extend(['--arch', self.arch])

        if self.auth_file:
            args.extend(['--authfile', self.auth_file])

        if self.username and self.password:
            cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
            args.extend(['--creds', cred_string])

        if self.validate_certs is not None:
            if self.validate_certs:
                args.append('--tls-verify')
            else:
                args.append('--tls-verify=false')

        if self.ca_cert_dir:
            args.extend(['--cert-dir', self.ca_cert_dir])

        if self.pull_extra_args:
            args.extend(shlex.split(self.pull_extra_args))

        rc, out, err = self._run(args, ignore_errors=True)
        if rc != 0:
            if not self.pull:
                self.module.fail_json(
                    msg='Failed to find image {image_name} locally, image pull set to {pull_bool}'.format(
                        pull_bool=self.pull, image_name=image_name))
            else:
                self.module.fail_json(
                    msg='Failed to pull image {image_name}'.format(image_name=image_name))
        return self.inspect_image(out.strip())

    def build_image(self, containerfile_hash):
        args = ['build']
        args.extend(['-t', self.image_name])

        if self.validate_certs is not None:
            if self.validate_certs:
                args.append('--tls-verify')
            else:
                args.append('--tls-verify=false')

        annotation = self.build.get('annotation')
        if annotation:
            for k, v in annotation.items():
                args.extend(['--annotation', '{k}={v}'.format(k=k, v=v)])

        if self.ca_cert_dir:
            args.extend(['--cert-dir', self.ca_cert_dir])

        if self.build.get('force_rm'):
            args.append('--force-rm')

        image_format = self.build.get('format')
        if image_format:
            args.extend(['--format', image_format])

        if self.arch:
            args.extend(['--arch', self.arch])

        if not self.build.get('cache'):
            args.append('--no-cache')

        if self.build.get('rm'):
            args.append('--rm')

        containerfile = self.build.get('file')
        if containerfile:
            args.extend(['--file', containerfile])
        container_file_txt = self.build.get('container_file')
        if container_file_txt:
            # create a temporarly file with the content of the Containerfile
            if self.path:
                container_file_path = os.path.join(self.path, 'Containerfile.generated_by_ansible_%s' % time.time())
            else:
                container_file_path = os.path.join(
                    tempfile.gettempdir(), 'Containerfile.generated_by_ansible_%s' % time.time())
            with open(container_file_path, 'w') as f:
                f.write(container_file_txt)
            args.extend(['--file', container_file_path])

        if containerfile_hash:
            args.extend(['--label', f"containerfile.hash={containerfile_hash}"])

        volume = self.build.get('volume')
        if volume:
            for v in volume:
                if v:
                    args.extend(['--volume', v])

        if self.auth_file:
            args.extend(['--authfile', self.auth_file])

        if self.username and self.password:
            cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
            args.extend(['--creds', cred_string])

        extra_args = self.build.get('extra_args')
        if extra_args:
            args.extend(shlex.split(extra_args))

        target = self.build.get('target')
        if target:
            args.extend(['--target', target])
        if self.path:
            args.append(self.path)

        rc, out, err = self._run(args, ignore_errors=True)
        if rc != 0:
            self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(
                image=self.image_name, out=out, err=err))
        # remove the temporary file if it was created
        if container_file_txt:
            os.remove(container_file_path)
        last_id = self._get_id_from_output(out, startswith='-->')
        return self.inspect_image(last_id), out + err

    def push_image(self):
        args = ['push']

        if self.validate_certs is not None:
            if self.validate_certs:
                args.append('--tls-verify')
            else:
                args.append('--tls-verify=false')

        if self.ca_cert_dir:
            args.extend(['--cert-dir', self.ca_cert_dir])

        if self.username and self.password:
            cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
            args.extend(['--creds', cred_string])

        if self.auth_file:
            args.extend(['--authfile', self.auth_file])

        if self.push_args.get('compress'):
            args.append('--compress')

        push_format = self.push_args.get('format')
        if push_format:
            args.extend(['--format', push_format])

        if self.push_args.get('remove_signatures'):
            args.append('--remove-signatures')

        sign_by_key = self.push_args.get('sign_by')
        if sign_by_key:
            args.extend(['--sign-by', sign_by_key])

        push_extra_args = self.push_args.get('extra_args')
        if push_extra_args:
            args.extend(shlex.split(push_extra_args))

        args.append(self.image_name)

        # Build the destination argument
        dest = self.push_args.get('dest')
        transport = self.push_args.get('transport')

        if dest is None:
            dest = self.image_name

        if transport:
            if transport == 'docker':
                dest_format_string = '{transport}://{dest}'
            elif transport == 'ostree':
                dest_format_string = '{transport}:{name}@{dest}'
            else:
                dest_format_string = '{transport}:{dest}'
                if transport == 'docker-daemon' and ":" not in dest:
                    dest_format_string = '{transport}:{dest}:latest'
            dest_string = dest_format_string.format(transport=transport, name=self.name, dest=dest)
        else:
            dest_string = dest
            # In case of dest as a repository with org name only, append image name to it
            if ":" not in dest and "@" not in dest and len(dest.rstrip("/").split("/")) == 2:
                dest_string = dest.rstrip("/") + "/" + self.image_name

        if "/" not in dest_string and "@" not in dest_string and "docker-daemon" not in dest_string:
            self.module.fail_json(msg="Destination must be a full URL or path to a directory with image name and tag.")

        args.append(dest_string)
        self.module.log("PODMAN-IMAGE-DEBUG: Pushing image {image_name} to {dest_string}".format(
            image_name=self.image_name, dest_string=dest_string))
        self.results['actions'].append(" ".join(args))
        self.results['changed'] = True
        out, err = '', ''
        if not self.module.check_mode:
            rc, out, err = self._run(args, ignore_errors=True)
            if rc != 0:
                self.module.fail_json(msg="Failed to push image {image_name}".format(
                    image_name=self.image_name),
                    stdout=out, stderr=err,
                    actions=self.results['actions'],
                    podman_actions=self.results['podman_actions'])

        return self.inspect_image(self.image_name), out + err

    def remove_image(self, image_name=None):
        if image_name is None:
            image_name = self.image_name

        args = ['rmi', image_name]
        if self.force:
            args.append('--force')
        rc, out, err = self._run(args, ignore_errors=True)
        if rc != 0:
            self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(
                image_name=image_name, err=err))
        return out

    def remove_image_id(self, image_id=None):
        if image_id is None:
            image_id = re.sub(':.*$', '', self.image_name)

        args = ['rmi', image_id]
        if self.force:
            args.append('--force')
        rc, out, err = self._run(args, ignore_errors=True)
        if rc != 0:
            self.module.fail_json(msg='Failed to remove image with id {image_id}. {err}'.format(
                image_id=image_id, err=err))
        return out


def parse_repository_tag(repo_name):
    parts = repo_name.rsplit('@', 1)
    if len(parts) == 2:
        return tuple(parts)
    parts = repo_name.rsplit(':', 1)
    if len(parts) == 2 and '/' not in parts[1]:
        return tuple(parts)
    return repo_name, None


def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(type='str', required=True),
            arch=dict(type='str'),
            tag=dict(type='str', default='latest'),
            pull=dict(type='bool', default=True),
            pull_extra_args=dict(type='str'),
            push=dict(type='bool', default=False),
            path=dict(type='str'),
            force=dict(type='bool', default=False),
            state=dict(type='str', default='present', choices=['absent', 'present', 'build', 'quadlet']),
            validate_certs=dict(type='bool', aliases=['tlsverify', 'tls_verify']),
            executable=dict(type='str', default='podman'),
            auth_file=dict(type='path', aliases=['authfile']),
            username=dict(type='str'),
            password=dict(type='str', no_log=True),
            ca_cert_dir=dict(type='path'),
            quadlet_dir=dict(type='path', required=False),
            quadlet_filename=dict(type='str'),
            quadlet_file_mode=dict(type='raw', required=False),
            quadlet_options=dict(type='list', elements='str', required=False),
            build=dict(
                type='dict',
                aliases=['build_args', 'buildargs'],
                default={},
                options=dict(
                    annotation=dict(type='dict'),
                    force_rm=dict(type='bool', default=False),
                    file=dict(type='path'),
                    container_file=dict(type='str'),
                    format=dict(
                        type='str',
                        choices=['oci', 'docker'],
                        default='oci'
                    ),
                    cache=dict(type='bool', default=True),
                    rm=dict(type='bool', default=True),
                    volume=dict(type='list', elements='str'),
                    extra_args=dict(type='str'),
                    target=dict(type='str'),
                ),
            ),
            push_args=dict(
                type='dict',
                default={},
                options=dict(
                    compress=dict(type='bool'),
                    format=dict(type='str', choices=['oci', 'v2s1', 'v2s2']),
                    remove_signatures=dict(type='bool'),
                    sign_by=dict(type='str'),
                    dest=dict(type='str', aliases=['destination'],),
                    extra_args=dict(type='str'),
                    transport=dict(
                        type='str',
                        choices=[
                            'dir',
                            'docker-archive',
                            'docker-daemon',
                            'oci-archive',
                            'ostree',
                            'docker'
                        ]
                    ),
                ),
            ),
        ),
        supports_check_mode=True,
        required_together=(
            ['username', 'password'],
        ),
        mutually_exclusive=(
            ['auth_file', 'username'],
            ['auth_file', 'password'],
        ),
    )

    results = dict(
        changed=False,
        actions=[],
        podman_actions=[],
        image={},
        stdout='',
    )

    PodmanImageManager(module, results)
    module.exit_json(**results)


if __name__ == '__main__':
    main()
