#!/usr/bin/env python
# pylint: disable=missing-docstring
#
# Copyright 2017 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import base64
import json
import os

from ansible.module_utils.basic import AnsibleModule


DOCUMENTATION = '''
---
module: docker_creds

short_description: Creates/updates a 'docker login' file in place of using 'docker login'

version_added: "2.4"

description:
    - This module creates a docker config.json file in the directory provided by 'path'
      on hosts that do not support 'docker login' but need the file present for
      registry authentication purposes of various other services.

options:
    path:
        description:
            - This is the message to send to the sample module
        required: true
    registry:
        description:
            - This is the registry the credentials are for.
        required: true
    username:
        description:
            - This is the username to authenticate to the registry with.
        required: true
    password:
        description:
            - This is the password to authenticate to the registry with.
        required: true

author:
    - "Michael Gugino <mgugino@redhat.com>"
'''

EXAMPLES = '''
# Pass in a message
- name: Place credentials in file
  docker_creds:
    path: /root/.docker
    registry: registry.example.com:443
    username: myuser
    password: mypassword
'''


def check_dest_dir_exists(module, dest):
    '''Check if dest dir is present and is a directory'''
    dir_exists = os.path.exists(dest)
    if dir_exists:
        if not os.path.isdir(dest):
            msg = "{} exists but is not a directory".format(dest)
            result = {'failed': True,
                      'changed': False,
                      'msg': msg,
                      'state': 'unknown'}
            module.fail_json(**result)
        else:
            return 1
    else:
        return 0


def create_dest_dir(module, dest):
    try:
        os.makedirs(dest, mode=0o700)
    except OSError as oserror:
        result = {'failed': True,
                  'changed': False,
                  'msg': str(oserror),
                  'state': 'unknown'}
        module.fail_json(**result)


def load_config_file(module, dest):
    '''load the config.json in directory dest'''
    conf_file_path = os.path.join(dest, 'config.json')
    if os.path.exists(conf_file_path):
        # Try to open the file and load json data
        try:
            with open(conf_file_path) as conf_file:
                data = conf_file.read()
            jdata = json.loads(data)

        except IOError as ioerror:
            result = {'failed': True,
                      'changed': False,
                      'msg': str(ioerror),
                      'state': 'unknown'}
            module.fail_json(**result)
        except ValueError as jsonerror:
            result = {'failed': True,
                      'changed': False,
                      'msg': str(jsonerror),
                      'state': 'unknown'}
            module.fail_json(**result)
        return jdata
    else:
        # File doesn't exist, we just return an empty dictionary.
        return {}


def update_config(docker_config, registry, username, password):
    '''Add our registry auth credentials into docker_config dict'''

    # Add anything that might be missing in our dictionary
    if 'auths' not in docker_config:
        docker_config['auths'] = {}
    if registry not in docker_config['auths']:
        docker_config['auths'][registry] = {}

    # base64 encode our username:password string
    encoded_data = base64.b64encode('{}:{}'.format(username, password).encode())

    # check if the same value is already present for idempotency.
    if 'auth' in docker_config['auths'][registry]:
        if docker_config['auths'][registry]['auth'] == encoded_data:
            # No need to go further, everything is already set in file.
            return False
    docker_config['auths'][registry]['auth'] = encoded_data
    return True


def write_config(module, docker_config, dest):
    '''Write updated credentials into dest/config.json'''
    conf_file_path = os.path.join(dest, 'config.json')
    try:
        with open(conf_file_path, 'w') as conf_file:
            json.dump(docker_config, conf_file, indent=8)
    except IOError as ioerror:
        result = {'failed': True,
                  'changed': False,
                  'msg': str(ioerror),
                  'state': 'unknown'}
        module.fail_json(**result)


def run_module():
    '''Run this module'''
    module_args = dict(
        path=dict(aliases=['dest', 'name'], required=True, type='path'),
        registry=dict(type='str', required=True),
        username=dict(type='str', required=True),
        password=dict(type='str', required=True, no_log=True)
    )

    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=False
    )

    # First, create our dest dir if necessary
    dest = module.params['path']
    registry = module.params['registry']
    username = module.params['username']
    password = module.params['password']

    if not check_dest_dir_exists(module, dest):
        create_dest_dir(module, dest)
        docker_config = {}
    else:
        # We want to scrape the contents of dest/config.json
        # in case there are other registries/settings already present.
        docker_config = load_config_file(module, dest)

    # Put the registry auth info into the config dict.
    changed = update_config(docker_config, registry, username, password)

    if changed:
        write_config(module, docker_config, dest)

    result = {'changed': changed}

    module.exit_json(**result)


def main():
    run_module()


if __name__ == '__main__':
    main()

@@ -4864,16 +4864,25 @@
 r_config
+.decode()
 , conf_f

fix python3 exception