Rework upload-forge role to use module

Renames the role to upload-forge because you can actually
run your own Forge server if you want.

This patch adds a custom module to the upload-forge
role that provides the "forge_upload" module.

This directly interacts with the a Forge API to upload the
module. The only dependency is that the python requests
module is installed.

Change-Id: I5749364bd2c29ad6df866c2bd5a3584c8419f709
This commit is contained in:
Tobias Urdin 2019-02-08 22:31:43 +01:00
parent dd8c88354e
commit a9322c04b6
10 changed files with 249 additions and 77 deletions

View File

@ -0,0 +1,24 @@
Upload puppet module tarball to a Forge server
This role requires the python requests module to be
installed where Ansible is executing this role.
**Role Variables**
.. zuul:rolevar:: forge_url
:default: https://forgeapi.puppet.com
The URL to the Puppet Forge API.
.. zuul:rolevar:: forge_username
Username to use to log in to Puppet Forge.
.. zuul:rolevar:: forge_password
Password to use to log in to Puppet Forge.
.. zuul:rolevar:: forge_tarball
Absolute path to the module tarball that should be
uploaded.

View File

View File

@ -0,0 +1,2 @@
---
forge_url: "https://forgeapi.puppet.com"

View File

View File

@ -0,0 +1,182 @@
#!/usr/bin/env python
# Copyright (c) 2019 Binero
# Author: Tobias Urdin <tobias.urdin@binero.se>
#
# 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.
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: forge_upload
short_description: Uploads a puppet module tarball to a Forge server.
description:
- "Uploads a puppet module tarball to a Forge server."
options:
username:
description:
- The username to the Forge account
required: true
password:
description:
- The password to the Forge account
required: true
tarball:
description:
- The absolute path to the tarball of the puppet module
that should be uploaded
required: true
forgeapi:
description:
- This base url to the Forge server API, defaults to
https://forgeapi.puppet.com
required: false
author:
- Tobias Urdin (@tobias-urdin)
'''
EXAMPLES = '''
- name: Upload module
forge_upload:
username: 'myuser'
password: 'mypass'
tarball: '/home/myuser/test/pkg/myuser-test-0.1.0.tar.gz'
'''
RETURN = '''
msg:
description: The output message from the module.
'''
from ansible.module_utils.basic import AnsibleModule # noqa
import os # noqa
import requests # noqa
# Client ID and secret from puppet-blacksmith
CLIENT_ID = 'b93eb708fd942cfc7b4ed71db6ce219b814954619dbe537ddfd208584e8cff8d'
CLIENT_SECRET = '216648059ad4afec3e4d77bd9e67817c095b2dcf94cdec18ac3d00584f863180' # noqa
FORGEAPI = 'https://forgeapi.puppet.com'
def _get_url(forgeapi, path):
path = path[1:] if path.startswith('/') else path
return '%s/%s' % (forgeapi, path)
def _forge_auth(forgeapi, username, password):
url = _get_url(forgeapi, '/oauth/token')
data = {
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'username': username,
'password': password,
'grant_type': 'password',
}
headers = {
'User-Agent': 'forge_upload-ansible-module/1.0',
}
response = requests.post(url, json=data, headers=headers)
return response
def _forge_upload(forgeapi, token, tarball):
url = _get_url(forgeapi, '/v2/releases')
data = {
'file': open(tarball, 'rb').read(),
}
headers = {
'User-Agent': 'forge_upload-ansible-module/1.0',
'Authorization': 'Bearer %s' % token,
}
response = requests.post(url, files=data, headers=headers)
return response
def run_module():
module_args = dict(
username=dict(type='str', required=True),
password=dict(type='str', required=True, no_log=True),
tarball=dict(type='str', required=True),
forgeapi=dict(type='str', default=FORGEAPI),
)
result = dict(
changed=False,
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
tarball = module.params['tarball']
if os.path.exists(tarball) is False:
module.fail_json(msg='Tarball %s does not exist' % tarball, **result)
resp = _forge_auth(module.params['forgeapi'],
module.params['username'],
module.params['password'])
if resp.status_code != 200:
msg = 'Forge API auth failed with code: %d' % resp.status_code
module.fail_json(msg=msg, **result)
if module.check_mode:
return result
auth = resp.json()
token = auth['access_token']
resp = _forge_upload(module.params['forgeapi'], token, tarball)
if resp.status_code == 409:
msg = 'Module %s already exists on Forge' % tarball
module.exit_json(msg=msg, **result)
if resp.status_code != 201:
try:
data = resp.json()
errors = ','.join(data['errors'])
except Exception:
errors = 'unknown'
msg = 'Forge API failed to upload tarball with code: %d errors: %s' % (
resp.status_code, errors)
module.fail_json(msg=msg, **result)
result['changed'] = True
module.exit_json(msg='Successfully uploaded tarball %s' % tarball,
**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
# Copyright (c) 2019 Binero
# Author: Tobias Urdin <tobias.urdin@binero.se>
#
# 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 testtools
from .forge_upload import _get_url
class TestForgeUpload(testtools.TestCase):
def test_get_url(self):
base_url = 'https://forgeapi.puppet.com'
expected = 'https://forgeapi.puppet.com/test'
self.assertEqual(_get_url(base_url, '/test'), expected)
self.assertEqual(_get_url(base_url, 'test'), expected)

View File

@ -0,0 +1,13 @@
- name: Check required variables
assert:
that:
- "forge_username is defined"
- "forge_password is defined"
- "forge_tarball is defined"
- name: Upload module to Forge
forge_upload:
username: "{{ forge_username }}"
password: "{{ forge_password }}"
tarball: "{{ forge_tarball }}"
forgeapi: "{{ forge_url }}"

View File

@ -1,22 +0,0 @@
Upload puppet module to Puppet Forge
**Role Variables**
.. zuul:rolevar:: puppet_module_dir
:default: {{ zuul.project.src_dir }}
The folder where the puppet module code is that it can
switch folder to.
.. zuul:rolevar:: blacksmith_forge_url
:default: https://forgeapi.puppetlabs.com
The URL to the Puppet Forge API.
.. zuul:rolevar:: blacksmith_forge_username
Username to use to log in to Puppet Forge.
.. zuul:rolevar:: blacksmith_forge_password
Password to use to log in to Puppet Forge.

View File

@ -1,3 +0,0 @@
---
puppet_module_dir: "{{ zuul.project.src_dir }}"
blacksmith_forge_url: "https://forgeapi.puppetlabs.com"

View File

@ -1,52 +0,0 @@
- name: Install ruby dependencies on RedHat/Suse based
package:
name:
- ruby-devel
- gcc-c++
- make
state: present
become: yes
when: ansible_os_family == "RedHat" or ansible_os_family == "Suse"
- name: Install ruby dependencies on Debian based
package:
name:
- ruby-dev
- g++
- make
state: present
become: yes
when: ansible_os_family == "Debian"
- name: Install required gems
gem:
name: "{{ item }}"
user_install: no
with_items:
- rake
- puppetlabs_spec_helper
- puppet-blacksmith
become: yes
# NOTE(tobias.urdin): The build task is needed because puppet-blacksmith
# doesn't provide a build task so it fails, we don't need one anyway since
# we have already built the module before this role is called.
- name: Install new Rakefile
copy:
content: |
namespace 'module' do
task 'build' do
end
end
require 'puppet_blacksmith/rake_tasks'
dest: "{{ puppet_module_dir }}/Rakefile"
- name: Publish puppet module
command: "rake module:push"
args:
chdir: "{{ puppet_module_dir }}"
environment:
BLACKSMITH_FORGE_URL: "{{ blacksmith_forge_url }}"
BLACKSMITH_FORGE_USERNAME: "{{ blacksmith_forge_username }}"
BLACKSMITH_FORGE_PASSWORD: "{{ blacksmith_forge_password }}"