forked from reanahub/reana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
release.py
212 lines (184 loc) · 7.53 KB
/
release.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# -*- coding: utf-8 -*-
#
# This file is part of REANA.
# Copyright (C) 2020 CERN.
#
# REANA is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""`reana-dev`'s release commands."""
import os
import sys
from time import sleep
import click
import semver
from reana.reana_dev.docker import docker_push
from reana.reana_dev.git import (
git_clean,
git_is_current_version_tagged,
is_last_commit_release_commit,
)
from reana.reana_dev.utils import (
display_message,
fetch_latest_pypi_version,
get_current_component_version_from_source_files,
get_current_tag,
is_component_dockerised,
parse_pep440_version,
run_command,
select_components,
translate_pep440_to_semver2,
)
def is_component_releasable(component, exit_code=False, display=False):
"""Determine whether a component is releasable.
Last commit should be a release commit and the new version should be git tagged.
:param component: Component to determine whether if it is releasable or not.
:param exit_code: Whether the program should exit with error exit code if
the condition is not met.
:param display: Whether error messages providing instructions on how to fix
the problem should be displayed to stdout.
:type component: str
:type exit_code: bool
:type display: bool
:rtype: bool
"""
is_releasable = True
error_message = ""
if not is_last_commit_release_commit(component):
error_message = "The last commit is not a release commit. Please use `reana-dev git-create-release-commit`."
is_releasable = False
if not git_is_current_version_tagged(component):
error_message = (
"The current version is not tagged. Please use `reana-dev git-tag`."
)
is_releasable = False
if error_message and display:
display_message(error_message, component)
if not is_releasable and exit_code:
sys.exit(1)
return is_releasable
@click.group()
def release_commands():
"""Release commands group."""
@click.option(
"--component",
"-c",
required=True,
multiple=True,
help="Which components? [name|CLUSTER]",
)
@click.option("--user", "-u", default="reanahub", help="DockerHub user name [reanahub]")
@click.option("--image-name", help="Should the component have a custom image name?")
@release_commands.command(name="release-docker")
@click.pass_context
def release_docker(ctx, component, user, image_name): # noqa: D301
"""Release a component on Docker Hub.
\b
:param components: The option ``component`` can be repeated. The value may
consist of:
* (1) standard component name such as
'reana-workflow-controller';
* (2) short component name such as 'r-w-controller';
* (3) special value '.' indicating component of the
current working directory;
* (4) special value 'CLUSTER' that will expand to
cover all REANA cluster components [default];
* (5) special value 'CLIENT' that will expand to
cover all REANA client components;
* (6) special value 'DEMO' that will expand
to include several runable REANA demo examples;
* (7) special value 'ALL' that will expand to include
all REANA repositories.
:type component: str
"""
components = select_components(component)
cannot_release_on_dockerhub = []
for component_ in components:
if not is_component_dockerised(component_):
cannot_release_on_dockerhub.append(component_)
is_component_releasable(component_, exit_code=True, display=True)
current_tag = get_current_tag(component_)
full_image_name = f"{user}/{image_name or component_}"
docker_tag = ""
if parse_pep440_version(current_tag):
docker_tag = translate_pep440_to_semver2(current_tag)
elif semver.VersionInfo.isvalid(current_tag):
docker_tag = current_tag
else:
display_message(
f"The component's latest tag ({current_tag}) is not a "
"valid version (nor PEP440 nor semver2 compliant).",
component_,
)
sys.exit(1)
run_command(
f"docker tag {full_image_name}:latest {full_image_name}:{docker_tag}",
component_,
)
ctx.invoke(docker_push, component=[component_], tag=docker_tag, user=user)
if cannot_release_on_dockerhub:
click.secho(
"The following components are not releasable on DockerHub: "
f"{', '.join(cannot_release_on_dockerhub)}",
fg="red",
)
sys.exit(1)
@click.option(
"--component",
"-c",
required=True,
multiple=True,
help="Which components? [name|CLUSTER]",
)
@click.option(
"--timeout",
required=True,
type=int,
default=90,
help="How many seconds should we wait to confirm successful PyPI release?",
)
@release_commands.command(name="release-pypi")
@click.pass_context
def release_pypi(ctx, component, timeout): # noqa: D301
"""Release a component on pypi.org.
\b
:param components: The option ``component`` can be repeated. The value may
consist of:
* (1) standard component name such as
'reana-workflow-controller';
* (2) short component name such as 'r-w-controller';
* (3) special value '.' indicating component of the
current working directory;
* (4) special value 'CLUSTER' that will expand to
cover all REANA cluster components [default];
* (5) special value 'CLIENT' that will expand to
cover all REANA client components;
* (6) special value 'DEMO' that will expand
to include several runable REANA demo examples;
* (7) special value 'ALL' that will expand to include
all REANA repositories.
:type component: str
"""
components = select_components(component)
username, password = os.getenv("TWINE_USERNAME"), os.getenv("TWINE_PASSWORD")
if not (username and password):
click.secho(
"Please set TWINE_USERNAME and TWINE_PASSWORD to release on PyPI.", fg="red"
)
sys.exit(1)
for component in components:
is_component_releasable(component, exit_code=True, display=True)
ctx.invoke(git_clean, component=[component])
for cmd in ["rm -rf dist", "python setup.py sdist", "twine upload ./dist/*"]:
run_command(cmd, component)
retry_interval = 15
time_elapsed = 0
while fetch_latest_pypi_version(
component
) != get_current_component_version_from_source_files(component):
sleep(retry_interval)
time_elapsed += retry_interval
if time_elapsed >= timeout:
click.secho("Something went wrong with the PyPI release.", fg="red")
sys.exit(1)
click.secho(f"{component} successfully released on PyPI", fg="green")
release_commands_list = list(release_commands.commands.values())