Skip to content

Commit 134c47f

Browse files
author
Matthias Balke
committed
implement HTTPS support for cloning (smarkets#225)
1 parent e465b36 commit 134c47f

File tree

7 files changed

+125
-36
lines changed

7 files changed

+125
-36
lines changed

README.md

+34-9
Original file line numberDiff line numberDiff line change
@@ -72,38 +72,40 @@ optional arguments:
7272
[env var: MARGE_AUTH_TOKEN_FILE] (default: None)
7373
--gitlab-url URL Your GitLab instance, e.g. "https://gitlab.example.com".
7474
[env var: MARGE_GITLAB_URL] (default: None)
75+
--use-https use HTTP(S) instead of SSH for GIT repository access
76+
[env var: MARGE_USE_HTTPS] (default: False)
7577
--ssh-key KEY The private ssh key for marge so it can clone/push.
7678
DISABLED because passing credentials on the command line is insecure:
7779
You can still set it via ENV variable or config file, or use "--ssh-key-file" flag.
7880
[env var: MARGE_SSH_KEY] (default: None)
7981
--ssh-key-file FILE Path to the private ssh key for marge so it can clone/push.
8082
[env var: MARGE_SSH_KEY_FILE] (default: None)
8183
--embargo INTERVAL[,..]
82-
Time(s) during which no merging is to take place, e.g. "Friday 1pm - Monday 9am"
83-
or "Fri 12:30 Europe/London - Mon 08:00 Europe/London"
84+
Time(s) during which no merging is to take place, e.g. "Friday 1pm - Monday 9am".
8485
[env var: MARGE_EMBARGO] (default: None)
8586
--use-merge-strategy Use git merge instead of git rebase to update the *source* branch (EXPERIMENTAL)
8687
If you need to use a strict no-rebase workflow (in most cases
8788
you don't want this, even if you configured gitlab to use merge requests
8889
to use merge commits on the *target* branch (the default).)
8990
[env var: MARGE_USE_MERGE_STRATEGY] (default: False)
91+
--rebase-remotely Instead of rebasing in a local clone of the repository, use GitLab's
92+
built-in rebase functionality, via their API. Note that Marge can't add
93+
information in the commits in this case.
94+
[env var: MARGE_REBASE_REMOTELY] (default: False)
9095
--add-tested Add "Tested: marge-bot <$MR_URL>" for the final commit on branch after it passed CI.
9196
[env var: MARGE_ADD_TESTED] (default: False)
9297
--batch Enable processing MRs in batches
9398
[env var: MARGE_BATCH] (default: False)
94-
--use-no-ff-batches Disable fast forwarding when merging MR batches.
95-
[env var: MARGE_USE_NO_FF_BATCHES] (default: False)
9699
--add-part-of Add "Part-of: <$MR_URL>" to each commit in MR.
97100
[env var: MARGE_ADD_PART_OF] (default: False)
98101
--add-reviewers Add "Reviewed-by: $approver" for each approver of MR to each commit in MR.
99102
[env var: MARGE_ADD_REVIEWERS] (default: False)
100103
--impersonate-approvers
101104
Marge-bot pushes effectively don't change approval status.
102105
[env var: MARGE_IMPERSONATE_APPROVERS] (default: False)
103-
--merge-order The order you want marge to merge its requests.
104-
As of earliest merge request creation time (created_at), update time (updated_at)
105-
or assigned to 'marge-bot' user time (assigned_at)
106-
[env var: MARGE_MERGE_ORDER] (default: created_at)
106+
--merge-order {created_at,updated_at,assigned_at}
107+
Order marge merges assigned requests. created_at (default), updated_at or assigned_at.
108+
[env var: MARGE_MERGE_ORDER] (default: created_at)
107109
--approval-reset-timeout APPROVAL_RESET_TIMEOUT
108110
How long to wait for approvals to reset after pushing.
109111
Only useful with the "new commits remove all approvals" option in a project's settings.
@@ -136,6 +138,11 @@ optional arguments:
136138
--cli Run marge-bot as a single CLI command, not as a long-running service.
137139
This may be used to run marge-bot in scheduled CI pipelines or cronjobs.
138140
[env var: MARGE_CLI] (default: False)
141+
--use-no-ff-batches Disable fast forwarding when merging MR batches [env var: MARGE_USE_NO_FF_BATCHES] (default: False)
142+
--use-merge-commit-batches
143+
Use merge commit when creating batches, so that the commits in the batch MR will be the same with in individual MRs. Requires sudo scope in the access token.
144+
[env var: MARGE_USE_MERGE_COMMIT_BATCHES] (default: False)
145+
--skip-ci-batches Skip CI when updating individual MRs when using batches [env var: MARGE_SKIP_CI_BATCHES] (default: False)
139146
```
140147
Here is a config file example
141148
```yaml
@@ -156,6 +163,8 @@ project-regexp: .*
156163
# choose one way of specifying the SSH key
157164
#ssh-key: KEY
158165
ssh-key-file: token.FILE
166+
# OR use HTTPS instead of SSH
167+
#use-https: true
159168
```
160169
For more information about configuring marge-bot see `--help`
161170
@@ -203,7 +212,7 @@ ssh-keygen -t ed25519 -C marge-bot@invalid -f marge-bot-ssh-key -P ''
203212
Add the public key (`marge-bot-ssh-key.pub`) to the user's `SSH Keys` in GitLab
204213
and keep the private one handy.
205214
206-
### Running marge-bot in docker (what we do)
215+
### Running marge-bot in docker using SSH (what we do)
207216
208217
Assuming you have already got docker installed, the quickest and most minimal
209218
way to run marge is like so (*but see note about passing secrets on the
@@ -256,6 +265,22 @@ may contain bugs.
256265
You can also specify a particular version as a tag, e.g.
257266
`smarkets/marge-bot:0.7.0`.
258267
268+
### Running marge-bot in docker using HTTPS
269+
270+
It is also possible to use Git over HTTPS instead of Git over SSH. To use HTTPS instead of SSH,
271+
add the `--use-https` flag and do not provide any SSH keys. Alternatively you can set the
272+
environment variable `MARGE_USE_HTTPS` or the config file property `use-https`.
273+
274+
```bash
275+
docker run --restart=on-failure \ # restart if marge crashes because GitLab is flaky
276+
-e MARGE_AUTH_TOKEN="$(cat marge-bot.token)" \
277+
smarkets/marge-bot \
278+
--use-https \
279+
--gitlab-url='http://your.gitlab.instance.com'
280+
```
281+
282+
HTTPS can be used using any other deployment technique as well.
283+
259284
### Running marge-bot in kubernetes
260285
It's also possible to run marge in kubernetes, e.g. here's how you use a ktmpl
261286
template:

dockerize.nix

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ in
3131
busybox
3232
gitMinimal
3333
openssh
34+
cacert
3435
] ++ [ marge ];
3536
config = {
3637
Entrypoint = [ "/bin/marge.app" ];
37-
Env = ["LANG=en_US.UTF-8" ''LOCALE_ARCHIVE=/lib/locale/locale-archive''];
38+
Env = ["LANG=en_US.UTF-8" ''LOCALE_ARCHIVE=/lib/locale/locale-archive'' "GIT_SSL_CAINFO=/etc/ssl/certs/ca-bundle.crt" "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"];
3839
};
3940
}

marge/app.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,14 @@ def regexp(str_regex):
7878
metavar='URL',
7979
help='Your GitLab instance, e.g. "https://gitlab.example.com".\n',
8080
)
81-
ssh_key_group = parser.add_mutually_exclusive_group(required=True)
82-
ssh_key_group.add_argument(
81+
repo_access = parser.add_mutually_exclusive_group(required=True)
82+
repo_access.add_argument(
83+
'--use-https',
84+
env_var='MARGE_USE_HTTPS',
85+
action='store_true',
86+
help='use HTTP(S) instead of SSH for GIT repository access\n',
87+
)
88+
repo_access.add_argument(
8389
'--ssh-key',
8490
type=str,
8591
metavar='KEY',
@@ -89,7 +95,7 @@ def regexp(str_regex):
8995
'You can still set it via ENV variable or config file, or use "--ssh-key-file" flag.\n'
9096
),
9197
)
92-
ssh_key_group.add_argument(
98+
repo_access.add_argument(
9399
'--ssh-key-file',
94100
type=str, # because we want a file location, not the content
95101
metavar='FILE',
@@ -261,7 +267,9 @@ def regexp(str_regex):
261267
@contextlib.contextmanager
262268
def _secret_auth_token_and_ssh_key(options):
263269
auth_token = options.auth_token or options.auth_token_file.readline().strip()
264-
if options.ssh_key_file:
270+
if options.use_https:
271+
yield auth_token, None
272+
elif options.ssh_key_file:
265273
yield auth_token, options.ssh_key_file
266274
else:
267275
with tempfile.NamedTemporaryFile(mode='w', prefix='ssh-key-') as tmp_ssh_key_file:
@@ -313,6 +321,8 @@ def main(args=None):
313321

314322
config = bot.BotConfig(
315323
user=user,
324+
use_https=options.use_https,
325+
auth_token=auth_token,
316326
ssh_key_file=ssh_key_file,
317327
project_regexp=options.project_regexp,
318328
git_timeout=options.git_timeout,

marge/bot.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,22 @@ def __init__(self, *, api, config):
3232

3333
def start(self):
3434
with TemporaryDirectory() as root_dir:
35-
repo_manager = store.RepoManager(
36-
user=self.user,
37-
root_dir=root_dir,
38-
ssh_key_file=self._config.ssh_key_file,
39-
timeout=self._config.git_timeout,
40-
reference=self._config.git_reference_repo,
41-
)
35+
if self._config.use_https:
36+
repo_manager = store.HttpsRepoManager(
37+
user=self.user,
38+
root_dir=root_dir,
39+
auth_token=self._config.auth_token,
40+
timeout=self._config.git_timeout,
41+
reference=self._config.git_reference_repo,
42+
)
43+
else:
44+
repo_manager = store.SshRepoManager(
45+
user=self.user,
46+
root_dir=root_dir,
47+
ssh_key_file=self._config.ssh_key_file,
48+
timeout=self._config.git_timeout,
49+
reference=self._config.git_reference_repo,
50+
)
4251
self._run(repo_manager)
4352

4453
@property
@@ -189,8 +198,8 @@ def _get_single_job(self, project, merge_request, repo, options):
189198

190199

191200
class BotConfig(namedtuple('BotConfig',
192-
'user ssh_key_file project_regexp merge_order merge_opts git_timeout ' +
193-
'git_reference_repo branch_regexp source_branch_regexp batch cli')):
201+
'user use_https auth_token ssh_key_file project_regexp merge_order merge_opts ' +
202+
'git_timeout git_reference_repo branch_regexp source_branch_regexp batch cli')):
194203
pass
195204

196205

marge/project.py

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def path_with_namespace(self):
7777
def ssh_url_to_repo(self):
7878
return self.info['ssh_url_to_repo']
7979

80+
@property
81+
def http_url_to_repo(self):
82+
return self.info['http_url_to_repo']
83+
8084
@property
8185
def merge_requests_enabled(self):
8286
return self.info['merge_requests_enabled']

marge/store.py

+52-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
1+
import re
12
import tempfile
23

34
from . import git
45

56

67
class RepoManager:
78

8-
def __init__(self, user, root_dir, ssh_key_file=None, timeout=None, reference=None):
9+
def __init__(self, user, root_dir, timeout=None, reference=None):
910
self._root_dir = root_dir
1011
self._user = user
11-
self._ssh_key_file = ssh_key_file
1212
self._repos = {}
1313
self._timeout = timeout
1414
self._reference = reference
1515

16+
def forget_repo(self, project):
17+
self._repos.pop(project.id, None)
18+
19+
@property
20+
def user(self):
21+
return self._user
22+
23+
@property
24+
def root_dir(self):
25+
return self._root_dir
26+
27+
28+
class SshRepoManager(RepoManager):
29+
30+
def __init__(self, user, root_dir, ssh_key_file=None, timeout=None, reference=None):
31+
super().__init__(user, root_dir, timeout, reference)
32+
self._ssh_key_file = ssh_key_file
33+
1634
def repo_for_project(self, project):
1735
repo = self._repos.get(project.id)
1836
if not repo or repo.remote_url != project.ssh_url_to_repo:
@@ -31,17 +49,39 @@ def repo_for_project(self, project):
3149

3250
return repo
3351

34-
def forget_repo(self, project):
35-
self._repos.pop(project.id, None)
36-
3752
@property
38-
def user(self):
39-
return self._user
53+
def ssh_key_file(self):
54+
return self._ssh_key_file
4055

41-
@property
42-
def root_dir(self):
43-
return self._root_dir
56+
57+
class HttpsRepoManager(RepoManager):
58+
59+
def __init__(self, user, root_dir, auth_token=None, timeout=None, reference=None):
60+
super().__init__(user, root_dir, timeout, reference)
61+
self._auth_token = auth_token
62+
63+
def repo_for_project(self, project):
64+
repo = self._repos.get(project.id)
65+
if not repo or repo.remote_url != project.http_url_to_repo:
66+
credentials = "oauth2:" + self._auth_token
67+
# insert token auth "oauth2:<auth_token>@"
68+
pattern = "(http(s)?://)"
69+
replacement = r"\1" + credentials + "@"
70+
repo_url = re.sub(pattern, replacement, project.http_url_to_repo, 1)
71+
local_repo_dir = tempfile.mkdtemp(dir=self._root_dir)
72+
73+
repo = git.Repo(repo_url, local_repo_dir, ssh_key_file=None,
74+
timeout=self._timeout, reference=self._reference)
75+
repo.clone()
76+
repo.config_user_info(
77+
user_email=self._user.email,
78+
user_name=self._user.name,
79+
)
80+
81+
self._repos[project.id] = repo
82+
83+
return repo
4484

4585
@property
46-
def ssh_key_file(self):
47-
return self._ssh_key_file
86+
def auth_token(self):
87+
return self._auth_token

tests/test_store.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class TestRepoManager:
1818
def setup_method(self, _method):
1919
user = marge.user.User(api=None, info=dict(USER_INFO, name='Peter Parker', email='pparker@bugle.com'))
2020
self.root_dir = tempfile.TemporaryDirectory()
21-
self.repo_manager = marge.store.RepoManager(
21+
self.repo_manager = marge.store.SshRepoManager(
2222
user=user, root_dir=self.root_dir.name, ssh_key_file='/ssh/key',
2323
)
2424

0 commit comments

Comments
 (0)