Add tripleo_deploy_artifacts

This module takes a list of urls of rpms or tar.gz files and applies
them to a system.

Change-Id: Ia1b998069a5c973813c187ea1efa9b5715a55f28
This commit is contained in:
Alex Schultz 2020-08-28 15:31:46 -06:00
parent 5702b7ba3d
commit f55f218535
3 changed files with 311 additions and 0 deletions

View File

@ -0,0 +1,14 @@
=================================
Module - tripleo_deploy_artifacts
=================================
This module provides for the following ansible plugin:
* tripleo_deploy_artifacts
.. ansibleautoplugin::
:module: tripleo_ansible/ansible_plugins/modules/tripleo_deploy_artifacts.py
:documentation: true
:examples: true

View File

@ -0,0 +1,149 @@
#!/usr/bin/python
# Copyright 2020 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
import os
import subprocess
import traceback
import urllib.request
import yaml
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: tripleo_deploy_artifacts
short_description: Deploy RPM/tar.gz artifact from a URL on a system
version_added: "2.9"
author: "Alex Schultz (@mwhahaha)"
description:
- Takes a set of urls as inputs, fetches their contents and deploys them
on the system.
options:
artifact_urls:
description:
- List of artifact urls to deploy
required: true
type: list
'''
RETURN = '''
'''
EXAMPLES = '''
- name: Deploy artifacts
tripleo_deploy_artifacts:
artifact_urls:
- http://example.com/foo.rpm
- http://example.com/foo.tar.gz
'''
def _get_filetype(filename):
cmd = "file -b " + filename
try:
r = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=True)
except Exception as e:
raise Exception('Unable to determine file type: %s' & e)
if 'RPM' in r.stdout:
return 'rpm'
elif 'gzip compressed data' in r.stdout:
return 'targz'
return 'UNKNOWN'
def deploy_rpm(filename):
rpm_filename = filename + '.rpm'
cmd = "dnf install -y " + rpm_filename
try:
os.rename(filename, rpm_filename)
_ = subprocess.run(cmd, shell=True, check=True, stderr=subprocess.PIPE,
universal_newlines=True)
except Exception as e:
raise Exception('Unable to install rpm: %s' % e)
finally:
if os.path.exists(rpm_filename):
os.unlink(rpm_filename)
def deploy_targz(filename):
cmd = "tar xvzf -C / " + filename
try:
_ = subprocess.run(cmd, shell=True, check=True, stderr=subprocess.PIPE,
universal_newlines=True)
except Exception as e:
raise Exception('Unable to install tar.gz: %s' % e)
finally:
if os.path.exists(filename):
os.unlink(filename)
def run(module):
results = dict(
changed=False
)
args = module.params
urls = args.get('artifact_urls')
tmpfile = None
# run command
for url in urls:
try:
(tmpfile, _) = urllib.request.urlretrieve(url)
filetype = _get_filetype(tmpfile)
if filetype == 'rpm':
deploy_rpm(tmpfile)
elif filetype == 'targz':
deploy_targz(tmpfile)
else:
results['failed'] = True
results['error'] = 'Invalid file format'
results['msg'] = ('Unable to determine file format for %s' %
url)
break
results['changed'] = True
except Exception as e:
results['failed'] = True
results['error'] = traceback.format_exc()
results['msg'] = "Unhandled exception: %s" % e
break
finally:
if tmpfile and os.path.exists(tmpfile):
os.unlink(tmpfile)
module.exit_json(**results)
def main():
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
supports_check_mode=False,
)
run(module)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,148 @@
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
from tripleo_ansible.ansible_plugins.modules import tripleo_deploy_artifacts
from tripleo_ansible.tests import base as tests_base
import mock
class TestTripleoDeployArtifacts(tests_base.TestCase):
@mock.patch('tripleo_ansible.ansible_plugins.modules.'
'tripleo_deploy_artifacts.deploy_targz')
@mock.patch('tripleo_ansible.ansible_plugins.modules.'
'tripleo_deploy_artifacts.deploy_rpm')
@mock.patch('tripleo_ansible.ansible_plugins.modules.'
'tripleo_deploy_artifacts._get_filetype')
@mock.patch('urllib.request.urlretrieve')
def test_run(self, mock_urlretrieve, mock_filetype, mock_rpm, mock_tgz):
module = mock.MagicMock()
module.params = {'artifact_urls': ['myrpm', 'mytgz']}
mock_exit = mock.MagicMock()
module.exit_json = mock_exit
mock_filetype.side_effect = ['rpm', 'targz']
mock_urlretrieve.side_effect = [('foo', None), ('bar', None)]
tripleo_deploy_artifacts.run(module)
self.assertEqual(mock_filetype.call_count, 2)
mock_filetype.has_calls([mock.call('myrpm'), mock.call('mytgz')])
mock_rpm.assert_called_once_with('foo')
mock_tgz.assert_called_once_with('bar')
mock_exit.assert_called_once_with(changed=True)
@mock.patch('urllib.request.urlretrieve')
def test_run_fail(self, mock_urlretrieve):
module = mock.MagicMock()
module.params = {'artifact_urls': ['myrpm', 'mytgz']}
mock_exit = mock.MagicMock()
module.exit_json = mock_exit
mock_urlretrieve.side_effect = Exception('meh')
tripleo_deploy_artifacts.run(module)
mock_exit.assert_called_once_with(changed=False, error=mock.ANY,
failed=True,
msg='Unhandled exception: meh')
@mock.patch('tripleo_ansible.ansible_plugins.modules.'
'tripleo_deploy_artifacts._get_filetype')
@mock.patch('urllib.request.urlretrieve')
def test_run_unknown(self, mock_urlretrieve, mock_filetype):
module = mock.MagicMock()
module.params = {'artifact_urls': ['bad']}
mock_filetype.return_value = 'UNKNOWN'
mock_exit = mock.MagicMock()
module.exit_json = mock_exit
mock_urlretrieve.return_value = ('foo', None)
tripleo_deploy_artifacts.run(module)
mock_exit.assert_called_once_with(changed=False,
error='Invalid file format',
failed=True,
msg=('Unable to determine file '
'format for bad'))
@mock.patch('subprocess.run')
def test_get_filetype_rpm(self, mock_run):
mock_rc = mock.MagicMock()
mock_rc.stdout = 'RPM v3.0 bin i386/x86_64 foo-0.0.1'
mock_run.return_value = mock_rc
self.assertEqual('rpm', tripleo_deploy_artifacts._get_filetype('foo'))
mock_run.assert_called_once_with('file -b foo', shell=True, stderr=-1,
stdout=-1, universal_newlines=True)
@mock.patch('subprocess.run')
def test_get_filetype_targz(self, mock_run):
mock_rc = mock.MagicMock()
mock_rc.stdout = ('gzip compressed data, last modified: Fri Mar 13 '
'22:10:46 2020, from Unix, original size modulo '
'2^32 4280320')
mock_run.return_value = mock_rc
self.assertEqual('targz',
tripleo_deploy_artifacts._get_filetype('foo'))
mock_run.assert_called_once_with('file -b foo', shell=True, stderr=-1,
stdout=-1, universal_newlines=True)
@mock.patch('subprocess.run')
def test_get_filetype_unknown(self, mock_run):
mock_rc = mock.MagicMock()
mock_rc.stdout = 'ASCII File'
mock_run.return_value = mock_rc
self.assertEqual('UNKNOWN',
tripleo_deploy_artifacts._get_filetype('foo'))
mock_run.assert_called_once_with('file -b foo', shell=True, stderr=-1,
stdout=-1, universal_newlines=True)
@mock.patch('subprocess.run')
def test_get_filetype_fail(self, mock_run):
mock_run.side_effect = Exception('meh')
self.assertRaises(Exception,
tripleo_deploy_artifacts._get_filetype,
'foo')
@mock.patch('os.rename')
@mock.patch('subprocess.run')
def test_deploy_rpm(self, mock_run, mock_rename):
tripleo_deploy_artifacts.deploy_rpm('foo')
mock_run.assert_called_once_with('dnf install -y foo.rpm', check=True,
shell=True, stderr=-1,
universal_newlines=True)
@mock.patch('os.unlink')
@mock.patch('os.path.exists')
@mock.patch('os.rename')
@mock.patch('subprocess.run')
def test_deploy_rpm_fail(self, mock_run, mock_rename, mock_exists,
mock_unlink):
mock_run.side_effect = Exception('meh')
mock_exists.return_value = True
self.assertRaises(Exception,
tripleo_deploy_artifacts.deploy_rpm,
'foo')
mock_unlink.assert_called_once_with('foo.rpm')
@mock.patch('subprocess.run')
def test_deploy_targz(self, mock_run):
tripleo_deploy_artifacts.deploy_targz('foo')
mock_run.assert_called_once_with('tar xvzf -C / foo', check=True,
shell=True, stderr=-1,
universal_newlines=True)
@mock.patch('os.unlink')
@mock.patch('os.path.exists')
@mock.patch('subprocess.run')
def test_deploy_targz_fail(self, mock_run, mock_exists, mock_unlink):
mock_run.side_effect = Exception('meh')
mock_exists.return_value = True
self.assertRaises(Exception,
tripleo_deploy_artifacts.deploy_targz,
'foo')
mock_unlink.assert_called_once_with('foo')