Compare commits
5 Commits
master
...
wip-woodpe
Author | SHA1 | Date |
---|---|---|
Aravinth Manivannan | b5ccbd5d77 | |
Aravinth Manivannan | e6222abca7 | |
Aravinth Manivannan | e97a0aa80e | |
Aravinth Manivannan | 27b0b74300 | |
Aravinth Manivannan | 8712b6d442 |
2
Makefile
2
Makefile
|
@ -9,7 +9,7 @@ endef
|
||||||
|
|
||||||
default: ## Run app
|
default: ## Run app
|
||||||
$(call run_migrations)
|
$(call run_migrations)
|
||||||
. ./venv/bin/activate && python manage.py runserver
|
. ./venv/bin/activate && python manage.py runserver 0.0.0.0:7000
|
||||||
|
|
||||||
coverage: ## Generate test coverage report
|
coverage: ## Generate test coverage report
|
||||||
. ./venv/bin/activate && coverage run manage.py test
|
. ./venv/bin/activate && coverage run manage.py test
|
||||||
|
|
|
@ -67,6 +67,10 @@ HOSTEA = {
|
||||||
# Repository dedicated for handling support
|
# Repository dedicated for handling support
|
||||||
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
|
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
|
||||||
"SUPPORT_REPOSITORY": "support",
|
"SUPPORT_REPOSITORY": "support",
|
||||||
|
"WOODPECKER": {
|
||||||
|
"HOST": "https://woodpecker.hostea.org", # meta Woodpecker CI instance
|
||||||
|
"TOKEN": "woodpecker_ci_token", # WOodpecker authentication token
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"INFRA": {
|
"INFRA": {
|
||||||
"HOSTEA_REPO": {
|
"HOSTEA_REPO": {
|
||||||
|
|
|
@ -180,6 +180,10 @@ HOSTEA = {
|
||||||
# Repository dedicated for handling support
|
# Repository dedicated for handling support
|
||||||
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
|
# ref: https://gitea.hostea.org/Hostea/july-mvp/issues/17
|
||||||
"SUPPORT_REPOSITORY": "support",
|
"SUPPORT_REPOSITORY": "support",
|
||||||
|
"WOODPECKER": {
|
||||||
|
"HOST": "https://woodpecker.hostea.org", # meta Woodpecker CI instance
|
||||||
|
"TOKEN": "woodpecker_ci_token", # WOodpecker authentication token
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"INFRA": {
|
"INFRA": {
|
||||||
"HOSTEA_REPO": {
|
"HOSTEA_REPO": {
|
||||||
|
|
|
@ -39,3 +39,6 @@ services:
|
||||||
#ports:
|
#ports:
|
||||||
# - "10025:10025"
|
# - "10025:10025"
|
||||||
# - "1080:1080"
|
# - "1080:1080"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
woodpecker-server-data:
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Copyright © 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
# Copyright © 2022 enough.community https://lab.enough.community/main/infrastructure/-/blob/master/AUTHORS
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from urillib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class CI:
|
||||||
|
def __init__(self):
|
||||||
|
self.host = settings.HOSTEA["META"]["WOODPECKER"]["HOST"]
|
||||||
|
# checking if woodpecker host is URL
|
||||||
|
_ = urlparse(self.host)
|
||||||
|
token = settings.HOSTEA["META"]["WOODPECKER"]["TOKEN"]
|
||||||
|
self.auth_header = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
self.w = requests.Session()
|
||||||
|
self.w.url = f"https://{self.host}"
|
||||||
|
r = self.w.get(self.w.url + "/authorize", allow_redirects=False)
|
||||||
|
|
||||||
|
location = self.gitea_browser.confirm_oauth(
|
||||||
|
r.headers["Location"], f"https://{self.hostname}/authorize"
|
||||||
|
)
|
||||||
|
r = self.w.get(location, allow_redirects=False)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Woodpecker CSRF
|
||||||
|
#
|
||||||
|
r = self.w.get(self.w.url + "/web-config.js", allow_redirects=False)
|
||||||
|
r.raise_for_status()
|
||||||
|
csrf = re.findall('window.WOODPECKER_CSRF = "(.*?)"', r.text)[0]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Woodpecker token
|
||||||
|
#
|
||||||
|
r = self.w.post(
|
||||||
|
self.w.url + "/api/user/token",
|
||||||
|
headers={"X-CSRF-TOKEN": csrf},
|
||||||
|
allow_redirects=False,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
self.token = r.text
|
||||||
|
self.w.headers = {"Authorization": f"Bearer {self.token}"}
|
||||||
|
|
||||||
|
def confirm_oauth(self, url, redirect):
|
||||||
|
logger.info(f"confirm oauth {url} redirect {redirect}")
|
||||||
|
r = self.g.get(url, allow_redirects=False)
|
||||||
|
r.raise_for_status()
|
||||||
|
if r.status_code == 200:
|
||||||
|
soup = BeautifulSoup(r.text, "html.parser")
|
||||||
|
data = {
|
||||||
|
"redirect_uri": redirect,
|
||||||
|
}
|
||||||
|
for input in soup.select(
|
||||||
|
'form[action="/login/oauth/grant"] input[type="hidden"]'
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
input.get("name") is None
|
||||||
|
or input.get("value") is None
|
||||||
|
or input.get("value") == ""
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
logger.info(f"collected hidden input {input['name']} {input['value']}")
|
||||||
|
data[input["name"]] = input["value"]
|
||||||
|
assert len(data) > 1, f"{data} has only one field, more are expected"
|
||||||
|
r = self.g.post(
|
||||||
|
self.g.url + "/login/oauth/grant", data=data, allow_redirects=False
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
logger.info("oauth confirmed")
|
||||||
|
elif r.status_code == 302:
|
||||||
|
logger.info("no confirmation required")
|
||||||
|
location = r.headers["Location"]
|
||||||
|
logger.info(f"going back to {location}")
|
||||||
|
assert location.startswith(redirect)
|
||||||
|
return location
|
||||||
|
|
||||||
|
def _build_list_url(self):
|
||||||
|
parsed = urlparse(self.host)
|
||||||
|
list_builds = "/api/repos/Hostea/dashboard/builds"
|
||||||
|
return (urlunparse((parsed.scheme, parsed.netloc, list_builds, "", "", "")),)
|
||||||
|
|
||||||
|
def logs(self):
|
||||||
|
resp = request.get(self._build_list_url(), headers=self.auth_header)
|
||||||
|
print(resp.json())
|
|
@ -26,6 +26,7 @@ class Gitea:
|
||||||
self.create_repository()
|
self.create_repository()
|
||||||
self.install_sso()
|
self.install_sso()
|
||||||
self.add_deploy_key()
|
self.add_deploy_key()
|
||||||
|
self.new_oauth2_app()
|
||||||
|
|
||||||
def __add_credentials_parser(self, parser):
|
def __add_credentials_parser(self, parser):
|
||||||
group = parser.add_argument_group("credentials", "User credentials")
|
group = parser.add_argument_group("credentials", "User credentials")
|
||||||
|
@ -121,6 +122,33 @@ class Gitea:
|
||||||
|
|
||||||
self.install_sso_parser.set_defaults(func=run)
|
self.install_sso_parser.set_defaults(func=run)
|
||||||
|
|
||||||
|
def new_oauth2_app(self):
|
||||||
|
def run(args, c: Session):
|
||||||
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
# checks if user exists
|
||||||
|
gitea.login()
|
||||||
|
creds = gitea.add_oauth_application(
|
||||||
|
name=args.app_name,
|
||||||
|
redirect_urls=[args.redirect_uri],
|
||||||
|
)
|
||||||
|
print(f"client_id: {creds[0]}client_secret: \n{creds[1]}")
|
||||||
|
|
||||||
|
self.install_oauth2_app = self.subparser.add_parser(
|
||||||
|
name="new_oauth2_app",
|
||||||
|
description="Create new oauth2 app on Gitea",
|
||||||
|
help="Create new oauth2 app on Gitea",
|
||||||
|
)
|
||||||
|
self.__add_credentials_parser(self.install_oauth2_app)
|
||||||
|
self.install_oauth2_app.add_argument(
|
||||||
|
"app_name", type=str, help="(Human readable)Name of the OAuth app"
|
||||||
|
)
|
||||||
|
self.install_oauth2_app.add_argument(
|
||||||
|
"redirect_uri",
|
||||||
|
type=str,
|
||||||
|
help="Redirect URI of the app",
|
||||||
|
)
|
||||||
|
self.install_oauth2_app.set_defaults(func=run)
|
||||||
|
|
||||||
def add_deploy_key(self):
|
def add_deploy_key(self):
|
||||||
def run(args, c: Session):
|
def run(args, c: Session):
|
||||||
gitea = gitea_from_args(args, c=c)
|
gitea = gitea_from_args(args, c=c)
|
||||||
|
|
|
@ -297,6 +297,15 @@ class Gitea:
|
||||||
resp = self.c.post(url, json=payload)
|
resp = self.c.post(url, json=payload)
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
|
|
||||||
|
def add_oauth_application(self, name: str, redirect_urls: [str]):
|
||||||
|
url = self.get_api_uri(f"/api/v1/user/applicaitons/oauth2")
|
||||||
|
payload = {"name": name, "redirect_urls": redirect_urls}
|
||||||
|
resp = self.c.post(url, json=payload)
|
||||||
|
print(f"new oauth status code: {resp.status_code}")
|
||||||
|
assert resp.status_code == 201
|
||||||
|
data = resp.json()
|
||||||
|
return (data["client_id"], data["client_secret"])
|
||||||
|
|
||||||
|
|
||||||
class ParseSSOLogin(HTMLParser):
|
class ParseSSOLogin(HTMLParser):
|
||||||
url: str = None
|
url: str = None
|
||||||
|
|
|
@ -13,13 +13,15 @@ then
|
||||||
MAILDEV_URL="http://smtp:1080"
|
MAILDEV_URL="http://smtp:1080"
|
||||||
GITEA_URL="http://gitea:3000"
|
GITEA_URL="http://gitea:3000"
|
||||||
GITEA_SSH_URL="ssh://git@gitea:22"
|
GITEA_SSH_URL="ssh://git@gitea:22"
|
||||||
|
WOODPECKER_URL="http://woodpecker:8000"
|
||||||
else
|
else
|
||||||
MAILDEV_URL="http://localhost:1080"
|
MAILDEV_URL="http://localhost:1080"
|
||||||
GITEA_URL="http://localhost:3000"
|
GITEA_URL="http://localhost:3000"
|
||||||
GITEA_SSH_URL="ssh://git@localhost:22"
|
GITEA_SSH_URL="ssh://git@localhost:22"
|
||||||
|
WOODPECKER_URL="http://localhost:8000"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
readonly DASHBOARD_URL="http://localhost:8000"
|
readonly DASHBOARD_URL="http://localhost:7000"
|
||||||
|
|
||||||
|
|
||||||
readonly DASHBOARD_OIDC_DISCOVERY_URL="$DASHBOARD_URL/o/.well-known/openid-configuration/"
|
readonly DASHBOARD_OIDC_DISCOVERY_URL="$DASHBOARD_URL/o/.well-known/openid-configuration/"
|
||||||
|
@ -36,6 +38,10 @@ readonly GITEA_HOSTEA_SSO_NAME=hostea-sso
|
||||||
readonly GITEA_OIDC_CALLBACK="$GITEA_URL/user/oauth2/$GITEA_HOSTEA_SSO_NAME/callback"
|
readonly GITEA_OIDC_CALLBACK="$GITEA_URL/user/oauth2/$GITEA_HOSTEA_SSO_NAME/callback"
|
||||||
readonly GITEA_HOSTEA_FLEET_DEPLOY_KEY="$(realpath tests/fleet-deploy-key.pub)"
|
readonly GITEA_HOSTEA_FLEET_DEPLOY_KEY="$(realpath tests/fleet-deploy-key.pub)"
|
||||||
readonly GITEA_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE="$(realpath tests/fleet-deploy-key)"
|
readonly GITEA_HOSTEA_FLEET_DEPLOY_KEY_PRIVATE="$(realpath tests/fleet-deploy-key)"
|
||||||
|
readonly GITEA_WOODPECKER_OAUTH_NAME="woodpecker"
|
||||||
|
|
||||||
|
readonly WOODPECKER_REDIRECT_URL="$WOODPECKER_URL/authorize"
|
||||||
|
|
||||||
|
|
||||||
readonly GITEA_HOSTEA_USERNAME=hostea
|
readonly GITEA_HOSTEA_USERNAME=hostea
|
||||||
readonly GITEA_HOSTEA_PASSWORD=supercomplicatedpassword
|
readonly GITEA_HOSTEA_PASSWORD=supercomplicatedpassword
|
||||||
|
@ -89,6 +95,13 @@ gitea_root(){
|
||||||
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
$GITEA_ROOT_EMAIL \
|
$GITEA_ROOT_EMAIL \
|
||||||
$GITEA_URL
|
$GITEA_URL
|
||||||
|
python -m integration \
|
||||||
|
gitea new_oauth2_app \
|
||||||
|
$GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
|
$GITEA_ROOT_EMAIL \
|
||||||
|
$GITEA_URL \
|
||||||
|
$GITEA_WOODPECKER_OAUTH_NAME \
|
||||||
|
$WOODPECKER_REDIRECT_URL
|
||||||
# python -m integration \
|
# python -m integration \
|
||||||
# gitea install_sso \
|
# gitea install_sso \
|
||||||
# $GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
# $GITEA_ROOT_USERNAME $GITEA_ROOT_PASSOWRD \
|
||||||
|
|
Loading…
Reference in New Issue