123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675 |
- # -*- coding: utf-8 -*-
- """
- (c) 2015-2018 - Copyright Red Hat Inc
- Authors:
- Patrick Uiterwijk <puiterwijk@redhat.com>
- """
- from __future__ import unicode_literals, absolute_import
- import datetime
- import functools
- import munch
- import unittest
- import shutil
- import subprocess
- import sys
- import tempfile
- import time
- import os
- import six
- import json
- import pygit2
- import requests
- from requests.adapters import HTTPAdapter
- from requests.packages.urllib3.util.retry import Retry
- from mock import patch, MagicMock
- sys.path.insert(
- 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
- )
- import pagure.lib.query
- import pagure.cli.admin
- import tests
- REPOSPANNER_CONFIG_TEMPLATE = """
- ---
- ca:
- path: %(path)s/repospanner/pki
- admin:
- url: https://localhost.localdomain:%(gitport)s/
- ca: %(path)s/repospanner/pki/ca.crt
- cert: %(path)s/repospanner/pki/admin.crt
- key: %(path)s/repospanner/pki/admin.key
- storage:
- state: %(path)s/repospanner/state
- git:
- type: tree
- clustered: true
- directory: %(path)s/repospanner/git
- listen:
- rpc: 127.0.0.1:%(rpcport)s
- http: 127.0.0.1:%(gitport)s
- certificates:
- ca: %(path)s/repospanner/pki/ca.crt
- client:
- cert: %(path)s/repospanner/pki/repospanner.localhost.crt
- key: %(path)s/repospanner/pki/repospanner.localhost.key
- server:
- default:
- cert: %(path)s/repospanner/pki/repospanner.localhost.crt
- key: %(path)s/repospanner/pki/repospanner.localhost.key
- hooks:
- debug: true
- bubblewrap:
- enabled: false
- unshare:
- - net
- - ipc
- - pid
- - uts
- share_net: false
- mount_proc: true
- mount_dev: true
- uid:
- gid:
- hostname: myhostname
- bind:
- ro_bind:
- - - /usr
- - /usr
- - - %(codepath)s
- - %(codepath)s
- - - %(path)s
- - %(path)s
- - - %(crosspath)s
- - %(crosspath)s
- symlink:
- - - usr/lib64
- - /lib64
- - - usr/bin
- - /bin
- runner: %(hookrunner_bin)s
- user: 0
- """
- class PagureRepoSpannerTests(tests.Modeltests):
- """ Tests for repoSpanner integration of pagure """
- repospanner_binary = None
- repospanner_runlog = None
- repospanner_proc = None
- def run_cacmd(self, logfile, *args):
- """ Run a repoSpanner CA command. """
- subprocess.check_call(
- [
- self.repospanner_binary,
- "--config",
- os.path.join(self.path, "repospanner", "config.yml"),
- # NEVER use this in a production system! It makes repeatable keys
- "ca",
- ]
- + list(args)
- + ["--very-insecure-weak-keys"],
- stdout=logfile,
- stderr=subprocess.STDOUT,
- )
- def setUp(self):
- """ set up the environment. """
- possible_paths = ["./repospanner", "/usr/bin/repospanner"]
- for option in possible_paths:
- option = os.path.abspath(option)
- if os.path.exists(option):
- self.repospanner_binary = option
- break
- if not self.repospanner_binary:
- raise unittest.SkipTest("repoSpanner not found")
- hookrunbins = [
- os.path.join(
- os.path.dirname(self.repospanner_binary), "repohookrunner"
- ),
- os.path.join("/usr", "libexec", "repohookrunner"),
- ]
- found = False
- for hookrunbin in hookrunbins:
- if os.path.exists(hookrunbin):
- found = True
- break
- if not found:
- raise Exception("repoSpanner found, but repohookrunner not")
- repobridgebins = [
- os.path.join(
- os.path.dirname(self.repospanner_binary), "repobridge"
- ),
- os.path.join("/usr", "libexec", "repobridge"),
- ]
- found = False
- for repobridgebin in repobridgebins:
- if os.path.exists(repobridgebin):
- found = True
- break
- if not found:
- raise Exception("repoSpanner found, but repobridge not")
- self.config_values["repobridge_binary"] = repobridgebin
- codepath = os.path.normpath(
- os.path.join(os.path.dirname(os.path.abspath(__file__)), "../")
- )
- # Only run the setUp() function if we are actually going ahead and run
- # this test. The reason being that otherwise, setUp will set up a
- # database, but because we "error out" from setUp, the tearDown()
- # function never gets called, leaving it behind.
- super(PagureRepoSpannerTests, self).setUp()
- # TODO: Find free ports
- configvals = {
- "path": self.path,
- "crosspath": self.dbfolder,
- "gitport": 8443 + sys.version_info.major,
- "rpcport": 8445 + sys.version_info.major,
- "codepath": codepath,
- "hookrunner_bin": hookrunbin,
- }
- os.mkdir(os.path.join(self.path, "repospanner"))
- cfgpath = os.path.join(self.path, "repospanner", "config.yml")
- with open(cfgpath, "w") as cfg:
- cfg.write(REPOSPANNER_CONFIG_TEMPLATE % configvals)
- with open(
- os.path.join(self.path, "repospanner", "keylog"), "w"
- ) as keylog:
- # Create the CA
- self.run_cacmd(
- keylog,
- "init",
- "localdomain",
- "--no-name-constraint",
- "--random-cn",
- )
- # Create the node cert
- self.run_cacmd(keylog, "node", "localhost", "repospanner")
- # Create the admin cert
- self.run_cacmd(
- keylog,
- "leaf",
- "admin",
- "--admin",
- "--region",
- "*",
- "--repo",
- "*",
- )
- # Create the Pagure cert
- self.run_cacmd(
- keylog,
- "leaf",
- "pagure",
- "--read",
- "--write",
- "--region",
- "*",
- "--repo",
- "*",
- )
- with open(
- os.path.join(self.path, "repospanner", "spawnlog"), "w"
- ) as spawnlog:
- # Initialize state
- subprocess.check_call(
- [
- self.repospanner_binary,
- "--config",
- cfgpath,
- "serve",
- "--spawn",
- ],
- stdout=spawnlog,
- stderr=subprocess.STDOUT,
- )
- self.repospanner_runlog = open(
- os.path.join(self.path, "repospanner", "runlog"), "w+"
- )
- try:
- self.repospanner_proc = subprocess.Popen(
- [
- self.repospanner_binary,
- "--config",
- cfgpath,
- "serve",
- "--debug",
- ],
- stdout=self.repospanner_runlog,
- stderr=subprocess.STDOUT,
- )
- except:
- # Make sure to clean up repoSpanner, since we did start it
- self.tearDown()
- raise
- attempts = 0
- while True:
- try:
- # Wait for the instance to become available
- resp = requests.get(
- "https://repospanner.localhost.localdomain:%d/"
- % configvals["gitport"],
- verify=os.path.join(
- self.path, "repospanner", "pki", "ca.crt"
- ),
- cert=(
- os.path.join(
- self.path, "repospanner", "pki", "pagure.crt"
- ),
- os.path.join(
- self.path, "repospanner", "pki", "pagure.key"
- ),
- ),
- )
- resp.raise_for_status()
- print("repoSpanner identification: %s" % resp.text)
- break
- except:
- if attempts < 5:
- attempts += 1
- time.sleep(1)
- continue
- # Make sure to clean up repoSpanner, since we did start it
- self.tearDown()
- raise
- # Upload the hook script to repoSpanner
- args = munch.Munch({"region": "default"})
- hookid = pagure.cli.admin.do_upload_repospanner_hooks(args)
- pagure.config.config["REPOSPANNER_REGIONS"]["default"]["hook"] = hookid
- def tearDown(self):
- """ Tear down the repoSpanner instance. """
- if self.repospanner_proc:
- # Tear down
- self.repospanner_proc.terminate()
- exitcode = self.repospanner_proc.wait()
- if exitcode != 0:
- print("repoSpanner exit code: %d" % exitcode)
- if self.repospanner_runlog:
- self.repospanner_runlog.close()
- super(PagureRepoSpannerTests, self).tearDown()
- def print_repospanner_log(fn):
- @functools.wraps(fn)
- def wrapper(self, *args, **kwargs):
- try:
- return fn(self, *args, **kwargs)
- finally:
- if self.repospanner_runlog:
- self.repospanner_runlog.seek(0, 0)
- print("repoSpanner log follows:")
- print(self.repospanner_runlog.read())
- return wrapper
- class PagureRepoSpannerTestsNewRepoDefault(PagureRepoSpannerTests):
- config_values = {
- "repospanner_new_repo": "'default'",
- "authbackend": "test_auth",
- }
- @print_repospanner_log
- @patch("pagure.ui.app.admin_session_timedout")
- def test_new_project(self, ast):
- """ Test creating a new repo by default on repoSpanner works. """
- ast.return_value = False
- user = tests.FakeUser(username="foo")
- with tests.user_set(self.app.application, user):
- output = self.app.get("/new/")
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn("<strong>Create new Project</strong>", output_text)
- data = {
- "name": "project-1",
- "description": "Project #1",
- "create_readme": "y",
- "csrf_token": self.get_csrf(),
- }
- output = self.app.post("/new/", data=data, follow_redirects=True)
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- '<div class="projectinfo my-3">\nProject #1', output_text
- )
- self.assertIn(
- "<title>Overview - project-1 - Pagure</title>", output_text
- )
- self.assertIn("Added the README", output_text)
- output = self.app.get("/project-1/settings")
- self.assertIn(
- "This repository is on repoSpanner region default",
- output.get_data(as_text=True),
- )
- with tests.user_set(
- self.app.application, tests.FakeUser(username="pingou")
- ):
- # Verify that for forking, Git auth status is ignored (hooks should not be run)
- self.set_auth_status(False)
- data = {"csrf_token": self.get_csrf()}
- output = self.app.post(
- "/do_fork/project-1", data=data, follow_redirects=True
- )
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- '<div class="projectinfo my-3">\nProject #1', output_text
- )
- self.assertIn(
- "<title>Overview - project-1 - Pagure</title>", output_text
- )
- self.assertIn("Added the README", output_text)
- self.assertIn(
- "/?next=http://localhost/fork/pingou/project-1", output_text
- )
- output = self.app.get("/fork/pingou/project-1/settings")
- self.assertIn(
- "This repository is on repoSpanner region default",
- output.get_data(as_text=True),
- )
- # Verify that only pseudo repos exist, and no on-disk repos got created
- repodirlist = os.listdir(os.path.join(self.path, "repos"))
- self.assertEqual(repodirlist, ["pseudo"])
- @print_repospanner_log
- @patch.dict(
- "pagure.config.config",
- {
- "ALLOW_HTTP_PULL_PUSH": True,
- "ALLOW_HTTP_PUSH": True,
- "HTTP_REPO_ACCESS_GITOLITE": False,
- },
- )
- def test_http_pull(self):
- """ Test that the HTTP pull endpoint works for repoSpanner. """
- tests.create_projects(self.session)
- tests.create_tokens(self.session)
- tests.create_tokens_acl(self.session)
- self.create_project_full("clonetest", {"create_readme": "y"})
- # Verify the new project is indeed on repoSpanner
- project = pagure.lib.query._get_project(self.session, "clonetest")
- self.assertTrue(project.is_on_repospanner)
- # Unfortunately, actually testing a git clone would need the app to
- # run on a TCP port, which the test environment doesn't do.
- output = self.app.get(
- "/clonetest.git/info/refs?service=git-upload-pack"
- )
- self.assertEqual(output.status_code, 200)
- self.assertEqual(
- output.content_type, "application/x-git-upload-pack-advertisement"
- )
- output_text = output.get_data(as_text=True)
- self.assertIn("# service=git-upload-pack", output_text)
- self.assertIn("agent=repoSpanner", output_text)
- self.assertIn("symref=HEAD:refs/heads/master", output_text)
- self.assertIn(" refs/heads/master\x00", output_text)
- output = self.app.post(
- "/clonetest.git/git-upload-pack",
- headers={"Content-Type": "application/x-git-upload-pack-request"},
- )
- self.assertEqual(output.status_code, 400)
- output_text = output.get_data(as_text=True)
- self.assertIn("Error processing your request", output_text)
- @print_repospanner_log
- @patch.dict(
- "pagure.config.config",
- {
- "ALLOW_HTTP_PULL_PUSH": True,
- "ALLOW_HTTP_PUSH": True,
- "HTTP_REPO_ACCESS_GITOLITE": False,
- },
- )
- def test_http_push(self):
- """ Test that the HTTP push endpoint works for repoSpanner. """
- tests.create_projects(self.session)
- tests.create_tokens(self.session)
- tests.create_tokens_acl(self.session)
- self.create_project_full("clonetest", {"create_readme": "y"})
- # Verify the new project is indeed on repoSpanner
- project = pagure.lib.query._get_project(self.session, "clonetest")
- self.assertTrue(project.is_on_repospanner)
- # Unfortunately, actually testing a git clone would need the app to
- # run on a TCP port, which the test environment doesn't do.
- output = self.app.get(
- "/clonetest.git/info/refs?service=git-receive-pack",
- environ_overrides={"REMOTE_USER": "pingou"},
- )
- self.assertEqual(output.status_code, 200)
- self.assertEqual(
- output.content_type, "application/x-git-receive-pack-advertisement"
- )
- output_text = output.get_data(as_text=True)
- self.assertIn("# service=git-receive-pack", output_text)
- self.assertIn("agent=repoSpanner", output_text)
- self.assertIn("symref=HEAD:refs/heads/master", output_text)
- self.assertIn(" refs/heads/master\x00", output_text)
- @print_repospanner_log
- @patch("pagure.ui.app.admin_session_timedout")
- def test_hooks(self, ast):
- """ Test hook setting and running works. """
- ast.return_value = False
- pagure.cli.admin.session = self.session
- user = tests.FakeUser(username="foo")
- with tests.user_set(self.app.application, user):
- data = {
- "name": "project-1",
- "description": "Project #1",
- "create_readme": "y",
- "csrf_token": self.get_csrf(),
- }
- output = self.app.post("/new/", data=data, follow_redirects=True)
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- '<div class="projectinfo my-3">\nProject #1', output_text
- )
- self.assertIn(
- "<title>Overview - project-1 - Pagure</title>", output_text
- )
- self.assertIn("Added the README", output_text)
- output = self.app.get("/project-1/settings")
- self.assertIn(
- "This repository is on repoSpanner region default",
- output.get_data(as_text=True),
- )
- # Check file before the commit:
- output = self.app.get("/project-1/raw/master/f/README.md")
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertEqual(output_text, "# project-1\n\nProject #1")
- with tests.user_set(self.app.application, user):
- # Set editing Denied
- self.set_auth_status(False)
- # Try to make an edit in the repo
- data = {
- "content": "foo\n bar\n baz",
- "commit_title": "test commit",
- "commit_message": "Online commit",
- "email": "foo@bar.com",
- "branch": "master",
- "csrf_token": self.get_csrf(),
- }
- output = self.app.post(
- "/project-1/edit/master/f/README.md",
- data=data,
- follow_redirects=True,
- )
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn("Remote hook declined the push: ", output_text)
- self.assertIn(
- "Denied push for ref 'refs/heads/master' for user 'foo'",
- output_text,
- )
- # Check file after the commit:
- output = self.app.get("/project-1/raw/master/f/README.md")
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertEqual(output_text, "# project-1\n\nProject #1")
- # Set editing Allowed
- self.set_auth_status(True)
- # Try to make an edit in the repo
- data = {
- "content": "foo\n bar\n baz",
- "commit_title": "test commit",
- "commit_message": "Online commit",
- "email": "foo@bar.com",
- "branch": "master",
- "csrf_token": self.get_csrf(),
- }
- output = self.app.post(
- "/project-1/edit/master/f/README.md",
- data=data,
- follow_redirects=True,
- )
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- "<title>Commits - project-1 - Pagure</title>", output_text
- )
- # Check file after the commit:
- output = self.app.get("/project-1/raw/master/f/README.md")
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertEqual(output_text, "foo\n bar\n baz")
- @print_repospanner_log
- @patch.dict(
- "pagure.config.config",
- {
- "PAGURE_ADMIN_USERS": ["pingou"],
- "ALLOW_ADMIN_IGNORE_EXISTING_REPOS": True,
- },
- )
- @patch("pagure.ui.app.admin_session_timedout")
- def test_adopt_project(self, ast):
- """ Test adopting a project in repoSpanner works. """
- ast.return_value = False
- user = tests.FakeUser(username="foo")
- with tests.user_set(self.app.application, user):
- output = self.app.get("/new/")
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn("<strong>Create new Project</strong>", output_text)
- data = {
- "name": "project-1",
- "description": "Project #1",
- "create_readme": "y",
- "csrf_token": self.get_csrf(),
- }
- output = self.app.post("/new/", data=data, follow_redirects=True)
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- '<div class="projectinfo my-3">\nProject #1', output_text
- )
- self.assertIn(
- "<title>Overview - project-1 - Pagure</title>", output_text
- )
- self.assertIn("Added the README", output_text)
- output = self.app.get("/project-1/settings")
- self.assertIn(
- "This repository is on repoSpanner region default",
- output.get_data(as_text=True),
- )
- # Delete the project instance so that the actual repo remains
- project = pagure.lib.query._get_project(self.session, "project-1")
- self.session.delete(project)
- self.session.commit()
- shutil.rmtree(os.path.join(self.path, "repos", "pseudo"))
- user = tests.FakeUser(username="pingou")
- with tests.user_set(self.app.application, user):
- output = self.app.get("/project-1/")
- self.assertEqual(output.status_code, 404)
- data = {
- "name": "project-1",
- "description": "Recreated project #1",
- "create_readme": "false",
- "csrf_token": self.get_csrf(),
- }
- output = self.app.post("/new/", data=data, follow_redirects=True)
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- "Repo pagure/main/project-1 already exists", output_text
- )
- data["ignore_existing_repos"] = "y"
- output = self.app.post("/new/", data=data, follow_redirects=True)
- self.assertEqual(output.status_code, 200)
- output_text = output.get_data(as_text=True)
- self.assertIn(
- '<div class="projectinfo my-3">\nRecreated project #1',
- output_text,
- )
- self.assertIn(
- "<title>Overview - project-1 - Pagure</title>", output_text
- )
- self.assertIn("Added the README", output_text)
- if __name__ == "__main__":
- unittest.main(verbosity=2)
|