From 0de92e0e4c85c876436ae21f61daee31c870705e Mon Sep 17 00:00:00 2001 From: Michael Gugino Date: Fri, 10 Nov 2017 20:51:00 -0500 Subject: Alternative method to create docker registry auth creds Currently, the command 'docker login' is run when using oreg with authentication. On some hosts, such as hosts configured to use system containers, the docker service is not running. 'docker login' will fail without the docker service running. This commit adds a module to idempotently add credentials to the registry credentials file without the use of 'docker login' Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1511374 --- roles/lib_utils/library/docker_creds.py | 207 ++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 roles/lib_utils/library/docker_creds.py (limited to 'roles/lib_utils') diff --git a/roles/lib_utils/library/docker_creds.py b/roles/lib_utils/library/docker_creds.py new file mode 100644 index 000000000..d4674845e --- /dev/null +++ b/roles/lib_utils/library/docker_creds.py @@ -0,0 +1,207 @@ +#!/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 " +''' + +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)) + + # 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() -- cgit v1.2.3