#!/usr/bin/env python3
# found on github, of all places, with an MIT license:
# MIT License
#
# Copyright (c) 2017 iNet Process
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import argparse
import os
import sys
from distutils.version import LooseVersion
from urllib.parse import urljoin, quote_plus
import requests
class GitlabReleaseError(RuntimeError):
pass
class GitlabRelease:
def __init__(self, debug=False):
self.env = {}
self.fetch_all_env()
self.api_project_url = self.get_api_project_url()
self.debug = debug
def fetch_env_var(self, name, msg):
if name not in self.env:
try:
self.env[name] = os.environ[name]
except KeyError:
raise GitlabReleaseError('Missing environment variable \'{}\': {}'.format(name, msg))
return self.env[name]
def is_gitlab_v9(self):
gl_version = self.fetch_env_var('CI_SERVER_VERSION', '')
return LooseVersion(gl_version) >= LooseVersion('9.0.0')
def get_tag(self):
tag_env = 'CI_BUILD_TAG'
if self.is_gitlab_v9():
tag_env = 'CI_COMMIT_TAG'
return self.fetch_env_var(tag_env, 'Releases can only be created on tag build.')
def fetch_all_env(self):
env = {
'CI_PROJECT_URL': '',
'CI_PROJECT_ID': '',
'GITLAB_ACCESS_TOKEN': "You must specifiy the private token linked to your GitLab account.\n"
'You probably need to add a GITLAB_ACCESS_TOKEN variable to your project.',
}
for var, msg in env.items():
self.fetch_env_var(var, msg)
def get_api_project_url(self):
api_version = 'v3'
if self.is_gitlab_v9():
api_version = 'v4'
return urljoin(self.env['CI_PROJECT_URL'],
'/api/{}/projects/{}'.format(api_version, self.env['CI_PROJECT_ID']))
def post_file(self, filename):
url = '/'.join((self.api_project_url, 'uploads'))
files = {'file': open(filename, 'rb')}
res = self.request('post', url, files=files)
return res.json()['markdown']
def fetch_links(self):
url = '/'.join((self.api_project_url, 'releases', self.get_tag(), 'assets/links'))
headers = {'Content-Type': 'application/json'}
res = self.request('get', url, headers=headers)
return res.json()
def set_links(self, links, update=False):
url = '/'.join((self.api_project_url, 'releases', self.get_tag(), 'assets/links'))
headers = {'Content-Type': 'application/json'}
method = 'put' if update else 'post'
res = self.request(method, url, headers=headers, json=links)
return res.json()
def set_release(self, message, update=False):
url = '/'.join((self.api_project_url, 'repository/tags', self.get_tag(), 'release'))
headers = {'Content-Type': 'application/json'}
body = {'description': message}
method = 'put' if update else 'post'
res = self.request(method, url, headers=headers, json=body)
return res.json()
def fetch_release(self):
url = '/'.join((self.api_project_url, 'repository/tags', self.get_tag()))
res = self.request('get', url)
return res.json()['release']
def request(self, method, url, **kwargs):
if 'headers' not in kwargs:
kwargs['headers'] = {}
kwargs['headers']['PRIVATE-TOKEN'] = self.env['GITLAB_ACCESS_TOKEN']
if self.debug:
print(url)
if 'body' in kwargs['headers']:
print(kwargs['headers']['body'])
res = requests.request(method, url, **kwargs)
res.raise_for_status()
if self.debug:
print(res.content)
return res
def create_release(self, message, files):
links = ['* ' + self.post_file(filename) for filename in files]
links.insert(0, message)
existing_release = self.fetch_release()
update = False
if existing_release is not None:
links = [existing_release['description'], '---'] + links
update = True
return self.set_release("\n\n".join(links), update)
if __name__ == '__main__':
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description='''
This program is intended to be used in a GitLab CI job in a Runner with Docker.
### 1. Configure your `.gitlab-ci.yml`
To make an automatic release you need to add something like this to the file
`.gitlab-ci.yml` in your project.
```yaml
stages:
- build
- publish
build:
stage: build
script:
- my_build_command
artifacts:
expire_in: '1 hour'
paths:
- compiled-$CI_BUILD_TAG.exe
- doc-$CI_BUILD_TAG.pdf
publish:
image: inetprocess/gitlab-release
stage: publish
only:
- tags
script:
- gitlab-release compiled-$CI_BUILD_TAG.exe doc-$CI_BUILD_TAG.pdf
```
### 2. Generate a personnal access token
Generate a new [Personal Access Token]
(https://docs.gitlab.com/ee/api/README.html#personal-access-tokens)
from your user profile.
### 3. Configure your project
Set a [secret variable](https://docs.gitlab.com/ce/ci/variables/#secret-variables)
in your project named `GITLAB_ACCESS_TOKEN` with the token you have generated in
the previous step.
'''
)
parser.add_argument('-m', '--message', default='',
help='Markdown message before the files list in the release note')
parser.add_argument('files', nargs='*',
help='Files to link in the release.')
parser.add_argument('-d', '--debug', action='store_true',
help='Print debug messages')
parser.add_argument('-l', '--links', action='store_true',
help='Print project links')
args = parser.parse_args()
#os.environ['CI_PROJECT_ID'] = 'tesch1%2Fcppduals'
#os.environ['CI_BUILD_TAG'] = 'v0.3.1'
#os.environ['CI_COMMIT_TAG'] = 'v0.3.1'
#os.environ['CI_PROJECT_URL'] = 'https://gitlab.com'
#os.environ['CI_SERVER_VERSION'] = '9.0.0'
#os.environ['GITLAB_ACCESS_TOKEN'] = ''
try:
release = GitlabRelease(args.debug)
#if args.links:
# print(release.fetch_links())
# sys.exit(0)
info = release.create_release(args.message, args.files)
print("Release: {tag_name} created.\n{description}".format_map(info))
except GitlabReleaseError as err:
print(err)
sys.exit(1)