mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Add gitea support
This commit is contained in:
@@ -133,6 +133,12 @@ _APP_VCS_GITHUB_APP_ID=
|
||||
_APP_VCS_GITHUB_CLIENT_ID=
|
||||
_APP_VCS_GITHUB_CLIENT_SECRET=
|
||||
_APP_VCS_GITHUB_WEBHOOK_SECRET=
|
||||
_APP_VCS_GITEA_ENDPOINT=http://gitea:3000
|
||||
_APP_VCS_GITEA_BROWSER_ENDPOINT=http://localhost:9510
|
||||
_APP_VCS_GITEA_CLIENT_ID=appwrite-gitea-client-id
|
||||
_APP_VCS_GITEA_CLIENT_SECRET=appwrite-gitea-client-secret
|
||||
_APP_VCS_GITEA_WEBHOOK_SECRET=gitea-webhook-secret
|
||||
_APP_VCS_WEBHOOK_URL=http://appwrite:80
|
||||
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
|
||||
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
|
||||
_APP_ASSISTANT_OPENAI_API_KEY=
|
||||
|
||||
@@ -1546,7 +1546,7 @@ $platformCollections = [
|
||||
'$id' => ID::custom('personalAccessToken'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
@@ -1568,7 +1568,7 @@ $platformCollections = [
|
||||
'$id' => ID::custom('personalRefreshToken'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
|
||||
@@ -101,6 +101,9 @@ const APP_SDK_PLATFORM_STATIC = 'static';
|
||||
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
|
||||
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
|
||||
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
|
||||
const APP_VCS_COMMIT_USERNAME = 'Appwrite';
|
||||
const APP_VCS_COMMIT_EMAIL = 'team@appwrite.io';
|
||||
const APP_VCS_PROVIDERS = ['github', 'gitea'];
|
||||
const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled';
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@
|
||||
"utopia-php/storage": "1.0.*",
|
||||
"utopia-php/system": "0.10.*",
|
||||
"utopia-php/telemetry": "0.2.*",
|
||||
"utopia-php/vcs": "3.*",
|
||||
"utopia-php/vcs": "dev-fix-gitea-branch-list as 3.99.99",
|
||||
"utopia-php/websocket": "1.0.*",
|
||||
"matomo/device-detector": "6.4.*",
|
||||
"dragonmantank/cron-expression": "3.4.*",
|
||||
|
||||
Generated
+36
-27
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4fb974e9843f6104e40396e7cad4a833",
|
||||
"content-hash": "cf36a6ac7c5041c9ec493333e6e05c52",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -1996,16 +1996,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.50",
|
||||
"version": "3.0.51",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
|
||||
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d59c94077f9c9915abb51ddb52ce85188ece1748",
|
||||
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2086,7 +2086,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.50"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.51"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2102,7 +2102,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-19T02:57:58+00:00"
|
||||
"time": "2026-04-10T01:33:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
@@ -3850,16 +3850,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "5.3.19",
|
||||
"version": "5.3.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691"
|
||||
"reference": "fad8e6b93c4d08cc611e41a828df3bbe0d9cfa24"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691",
|
||||
"reference": "72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/fad8e6b93c4d08cc611e41a828df3bbe0d9cfa24",
|
||||
"reference": "fad8e6b93c4d08cc611e41a828df3bbe0d9cfa24",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3903,9 +3903,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.19"
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.20"
|
||||
},
|
||||
"time": "2026-03-31T15:52:08+00:00"
|
||||
"time": "2026-04-10T08:27:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -5225,16 +5225,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
"version": "3.1.0",
|
||||
"version": "dev-fix-gitea-branch-list",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/vcs.git",
|
||||
"reference": "03b76ad5fd01bc50f809915bca6ff0745ea913af"
|
||||
"reference": "ff7feacff8cd3e23de77dd360ffb931d984b8c03"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/03b76ad5fd01bc50f809915bca6ff0745ea913af",
|
||||
"reference": "03b76ad5fd01bc50f809915bca6ff0745ea913af",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/ff7feacff8cd3e23de77dd360ffb931d984b8c03",
|
||||
"reference": "ff7feacff8cd3e23de77dd360ffb931d984b8c03",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5268,9 +5268,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/vcs/issues",
|
||||
"source": "https://github.com/utopia-php/vcs/tree/3.1.0"
|
||||
"source": "https://github.com/utopia-php/vcs/tree/fix-gitea-branch-list"
|
||||
},
|
||||
"time": "2026-03-24T08:49:14+00:00"
|
||||
"time": "2026-04-10T14:43:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
@@ -5448,16 +5448,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.17.7",
|
||||
"version": "1.17.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "291471d04c3f0e7b9fcc46668a6255a4c0f2947e"
|
||||
"reference": "b7109e13cec89ed56ad80111973b12646e4eb5f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/291471d04c3f0e7b9fcc46668a6255a4c0f2947e",
|
||||
"reference": "291471d04c3f0e7b9fcc46668a6255a4c0f2947e",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/b7109e13cec89ed56ad80111973b12646e4eb5f7",
|
||||
"reference": "b7109e13cec89ed56ad80111973b12646e4eb5f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5493,9 +5493,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.17.7"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.17.8"
|
||||
},
|
||||
"time": "2026-04-08T08:51:05+00:00"
|
||||
"time": "2026-04-09T05:08:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
@@ -8426,9 +8426,18 @@
|
||||
"time": "2024-11-07T12:36:22+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "utopia-php/vcs",
|
||||
"version": "dev-fix-gitea-branch-list",
|
||||
"alias": "3.99.99",
|
||||
"alias_normalized": "3.99.99.0"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {},
|
||||
"stability-flags": {
|
||||
"utopia-php/vcs": 20
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
||||
+82
-1
@@ -232,6 +232,12 @@ services:
|
||||
- _APP_VCS_GITHUB_WEBHOOK_SECRET
|
||||
- _APP_VCS_GITHUB_CLIENT_SECRET
|
||||
- _APP_VCS_GITHUB_CLIENT_ID
|
||||
- _APP_VCS_GITEA_ENDPOINT
|
||||
- _APP_VCS_GITEA_BROWSER_ENDPOINT
|
||||
- _APP_VCS_GITEA_CLIENT_ID
|
||||
- _APP_VCS_GITEA_CLIENT_SECRET
|
||||
- _APP_VCS_GITEA_WEBHOOK_SECRET
|
||||
- _APP_VCS_WEBHOOK_URL
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
@@ -546,6 +552,11 @@ services:
|
||||
- _APP_VCS_GITHUB_APP_NAME
|
||||
- _APP_VCS_GITHUB_PRIVATE_KEY
|
||||
- _APP_VCS_GITHUB_APP_ID
|
||||
- _APP_VCS_GITEA_ENDPOINT
|
||||
- _APP_VCS_GITEA_BROWSER_ENDPOINT
|
||||
- _APP_VCS_GITEA_CLIENT_ID
|
||||
- _APP_VCS_GITEA_CLIENT_SECRET
|
||||
- _APP_VCS_GITEA_WEBHOOK_SECRET
|
||||
- _APP_FUNCTIONS_TIMEOUT
|
||||
- _APP_SITES_TIMEOUT
|
||||
- _APP_COMPUTE_BUILD_TIMEOUT
|
||||
@@ -1447,6 +1458,75 @@ services:
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
gitea:
|
||||
image: gitea/gitea:1.21.5
|
||||
container_name: appwrite-gitea
|
||||
<<: *x-logging
|
||||
ports:
|
||||
- "9510:3000"
|
||||
volumes:
|
||||
- appwrite-gitea:/data
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__database__DB_TYPE=sqlite3
|
||||
- GITEA__security__INSTALL_LOCK=true
|
||||
- GITEA__webhook__ALLOWED_HOST_LIST=*
|
||||
- GITEA__webhook__SKIP_TLS_VERIFY=true
|
||||
- GITEA__webhook__DELIVER_TIMEOUT=10
|
||||
- GITEA__server__ROOT_URL=http://gitea:3000/
|
||||
- GITEA__server__LOCAL_ROOT_URL=http://gitea:3000/
|
||||
networks:
|
||||
- appwrite
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
|
||||
gitea-setup:
|
||||
image: gitea/gitea:1.21.5
|
||||
container_name: appwrite-gitea-setup
|
||||
<<: *x-logging
|
||||
depends_on:
|
||||
gitea:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- appwrite-gitea:/data
|
||||
environment:
|
||||
- GITEA_ADMIN_USERNAME=appwrite
|
||||
- GITEA_ADMIN_PASSWORD=password
|
||||
- GITEA_ADMIN_EMAIL=admin@appwrite.local
|
||||
- GITEA_OAUTH2_CLIENT_ID=${_APP_VCS_GITEA_CLIENT_ID:-appwrite-gitea-client-id}
|
||||
- GITEA_OAUTH2_CLIENT_SECRET=${_APP_VCS_GITEA_CLIENT_SECRET:-appwrite-gitea-client-secret}
|
||||
- GITEA_OAUTH2_REDIRECT_URIS=["http://localhost/v1/vcs/gitea/callback","http://localhost:9501/v1/vcs/gitea/callback","http://localhost:80/v1/vcs/gitea/callback"]
|
||||
entrypoint: >
|
||||
sh -c "
|
||||
echo 'Setting up Gitea...' &&
|
||||
su git -c 'gitea admin user create --username $$GITEA_ADMIN_USERNAME --password $$GITEA_ADMIN_PASSWORD --email $$GITEA_ADMIN_EMAIL --admin --must-change-password=false' 2>/dev/null || true &&
|
||||
TOKEN=$$(su git -c 'gitea admin user generate-access-token --username $$GITEA_ADMIN_USERNAME --token-name appwrite-token --scopes all --raw' 2>/dev/null) &&
|
||||
echo $$TOKEN > /data/gitea/token.txt &&
|
||||
echo 'Creating OAuth2 application...' &&
|
||||
apk add --no-cache apache2-utils sqlite > /dev/null 2>&1 &&
|
||||
SECRET_HASH=$$(htpasswd -nbBC 10 '' \"$$GITEA_OAUTH2_CLIENT_SECRET\" | cut -d: -f2) &&
|
||||
USER_ID=$$(sqlite3 /data/gitea/gitea.db \"SELECT id FROM user WHERE lower_name='$$GITEA_ADMIN_USERNAME' LIMIT 1;\") &&
|
||||
TIMESTAMP=$$(date +%s) &&
|
||||
sqlite3 /data/gitea/gitea.db \"DELETE FROM oauth2_application WHERE name='Appwrite';\" &&
|
||||
sqlite3 /data/gitea/gitea.db \"INSERT INTO oauth2_application (uid, name, client_id, client_secret, confidential_client, redirect_uris, created_unix, updated_unix) VALUES ($$USER_ID, 'Appwrite', '$$GITEA_OAUTH2_CLIENT_ID', '$$SECRET_HASH', 1, '$$GITEA_OAUTH2_REDIRECT_URIS', $$TIMESTAMP, $$TIMESTAMP);\" &&
|
||||
echo '' &&
|
||||
echo '========================================' &&
|
||||
echo 'Gitea setup complete!' &&
|
||||
echo '========================================' &&
|
||||
echo \"Gitea URL: http://localhost:9510\" &&
|
||||
echo \"Admin user: $$GITEA_ADMIN_USERNAME\" &&
|
||||
echo \"Admin password: $$GITEA_ADMIN_PASSWORD\" &&
|
||||
echo \"OAuth2 Client ID: $$GITEA_OAUTH2_CLIENT_ID\" &&
|
||||
echo '========================================'
|
||||
"
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
# Dev tools (adminer, redis-insight, mongo-express, graphql-explorer)
|
||||
# are defined in docker-compose.override.yml
|
||||
|
||||
@@ -1477,4 +1557,5 @@ volumes:
|
||||
appwrite-sites:
|
||||
appwrite-builds:
|
||||
appwrite-config:
|
||||
appwrite-models:
|
||||
appwrite-models:
|
||||
appwrite-gitea:
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
class Gitea extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $endpoint;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [];
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @param string $appSecret
|
||||
* @param string $callback
|
||||
* @param array $state
|
||||
* @param array $scopes
|
||||
* @param string $endpoint Base URL of the Gitea instance (e.g. https://gitea.example.com)
|
||||
*/
|
||||
public function __construct(string $appId, string $appSecret, string $callback, array $state = [], array $scopes = [], string $endpoint = '')
|
||||
{
|
||||
$this->endpoint = rtrim($endpoint, '/');
|
||||
parent::__construct($appId, $appSecret, $callback, $state, $scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'gitea';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return $this->endpoint . '/login/oauth/authorize?' . \http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'response_type' => 'code',
|
||||
'state' => \json_encode($this->state),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/login/oauth/access_token',
|
||||
['Content-Type: application/x-www-form-urlencoded', 'Accept: application/json'],
|
||||
\http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'redirect_uri' => $this->callback,
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
])
|
||||
);
|
||||
|
||||
$this->tokens = \json_decode($response, true) ?? [];
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/login/oauth/access_token',
|
||||
['Content-Type: application/x-www-form-urlencoded', 'Accept: application/json'],
|
||||
\http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
])
|
||||
);
|
||||
|
||||
$this->tokens = \json_decode($response, true) ?? [];
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return \strval($user['id'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['full_name'] ?? $user['login'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserSlug(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['login'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$response = $this->request('GET', $this->endpoint . '/api/v1/user', ['Authorization: token ' . \urlencode($accessToken)]);
|
||||
$this->user = \json_decode($response, true) ?? [];
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
* @param string $repositoryName
|
||||
* @param bool $private
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function createRepository(string $accessToken, string $repositoryName, bool $private): array
|
||||
{
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/api/v1/user/repos',
|
||||
['Authorization: token ' . \urlencode($accessToken), 'Content-Type: application/json'],
|
||||
\json_encode([
|
||||
'name' => $repositoryName,
|
||||
'private' => $private,
|
||||
])
|
||||
);
|
||||
|
||||
return \json_decode($response, true) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,11 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\System\System;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Adapter\Git;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
class Base extends Action
|
||||
@@ -57,18 +59,16 @@ class Base extends Action
|
||||
return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT;
|
||||
}
|
||||
|
||||
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document
|
||||
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, Git $vcs, bool $activate, string $referenceType = 'branch', string $reference = ''): Document
|
||||
{
|
||||
$deploymentId = ID::unique();
|
||||
$entrypoint = $function->getAttribute('entrypoint', '');
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -77,33 +77,30 @@ class Base extends Action
|
||||
}
|
||||
|
||||
$commitDetails = [];
|
||||
$branchUrl = "";
|
||||
$providerBranch = "";
|
||||
$branchUrl = '';
|
||||
$providerBranch = '';
|
||||
|
||||
// TODO: Support tag in future
|
||||
if ($referenceType === 'branch') {
|
||||
$providerBranch = empty($reference) ? $function->getAttribute('providerBranch', 'main') : $reference;
|
||||
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
|
||||
$branchUrl = VcsFactory::getBranchUrl($provider, $owner, $repositoryName, $providerBranch);
|
||||
try {
|
||||
$commitDetails = $github->getLatestCommit($owner, $repositoryName, $providerBranch);
|
||||
$commitDetails = $vcs->getLatestCommit($owner, $repositoryName, $providerBranch);
|
||||
} catch (\Throwable $error) {
|
||||
// Ignore; deployment can continue
|
||||
}
|
||||
} elseif ($referenceType === 'commit') {
|
||||
try {
|
||||
$commitDetails = $github->getCommit($owner, $repositoryName, $reference);
|
||||
$commitDetails = $vcs->getCommit($owner, $repositoryName, $reference);
|
||||
} catch (\Throwable $error) {
|
||||
// Ignore; deployment can continue
|
||||
}
|
||||
} else {
|
||||
// Fallback till we have tag support here
|
||||
// Goal is to set providerBranch, so build worker knows what to clone as base
|
||||
// Without this, clone command would be cloning empty branch, and failing
|
||||
$providerBranch = $function->getAttribute('providerBranch', 'main');
|
||||
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
|
||||
$branchUrl = VcsFactory::getBranchUrl($provider, $owner, $repositoryName, $providerBranch);
|
||||
}
|
||||
|
||||
$repositoryUrl = "https://github.com/$owner/$repositoryName";
|
||||
$repositoryUrl = VcsFactory::getRepoUrl($provider, $owner, $repositoryName);
|
||||
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
@@ -159,17 +156,15 @@ class Base extends Action
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, array $platform, string $referenceType = 'branch', string $reference = ''): Document
|
||||
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, Git $vcs, bool $activate, Authorization $authorization, array $platform, string $referenceType = 'branch', string $reference = ''): Document
|
||||
{
|
||||
$deploymentId = ID::unique();
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$providerRepositoryId = $site->getAttribute('providerRepositoryId', '');
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -178,33 +173,30 @@ class Base extends Action
|
||||
}
|
||||
|
||||
$commitDetails = [];
|
||||
$branchUrl = "";
|
||||
$providerBranch = "";
|
||||
$branchUrl = '';
|
||||
$providerBranch = '';
|
||||
|
||||
// TODO: Support tag in future
|
||||
if ($referenceType === 'branch') {
|
||||
$providerBranch = empty($reference) ? $site->getAttribute('providerBranch', 'main') : $reference;
|
||||
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
|
||||
$branchUrl = VcsFactory::getBranchUrl($provider, $owner, $repositoryName, $providerBranch);
|
||||
try {
|
||||
$commitDetails = $github->getLatestCommit($owner, $repositoryName, $providerBranch);
|
||||
$commitDetails = $vcs->getLatestCommit($owner, $repositoryName, $providerBranch);
|
||||
} catch (\Throwable $error) {
|
||||
// Ignore; deployment can continue
|
||||
}
|
||||
} elseif ($referenceType === 'commit') {
|
||||
try {
|
||||
$commitDetails = $github->getCommit($owner, $repositoryName, $reference);
|
||||
$commitDetails = $vcs->getCommit($owner, $repositoryName, $reference);
|
||||
} catch (\Throwable $error) {
|
||||
// Ignore; deployment can continue
|
||||
}
|
||||
} else {
|
||||
// Fallback till we have tag support here
|
||||
// Goal is to set providerBranch, so build worker knows what to clone as base
|
||||
// Without this, clone command would be cloning empty branch, and failing
|
||||
$providerBranch = $site->getAttribute('providerBranch', 'main');
|
||||
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
|
||||
$branchUrl = VcsFactory::getBranchUrl($provider, $owner, $repositoryName, $providerBranch);
|
||||
}
|
||||
|
||||
$repositoryUrl = "https://github.com/$owner/$repositoryName";
|
||||
$repositoryUrl = VcsFactory::getRepoUrl($provider, $owner, $repositoryName);
|
||||
|
||||
$commands = [];
|
||||
if (!empty($site->getAttribute('installCommand', ''))) {
|
||||
|
||||
@@ -7,6 +7,7 @@ use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Domain;
|
||||
@@ -66,11 +67,7 @@ class Get extends Action
|
||||
|
||||
$isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid;
|
||||
|
||||
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''));
|
||||
$isVcsEnabled = !empty(VcsFactory::getConfiguredProviders());
|
||||
|
||||
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
|
||||
|
||||
@@ -86,6 +83,7 @@ class Get extends Action
|
||||
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
|
||||
'_APP_VCS_ENABLED' => $isVcsEnabled,
|
||||
'_APP_VCS_PROVIDERS' => VcsFactory::getConfiguredProviders(),
|
||||
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
|
||||
'_APP_DOMAIN_SITES' => $platform['sitesDomain'],
|
||||
|
||||
@@ -20,10 +20,11 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class Create extends Base
|
||||
{
|
||||
@@ -77,7 +78,7 @@ class Create extends Base
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('queueForBuilds')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -97,7 +98,7 @@ class Create extends Base
|
||||
Event $queueForEvents,
|
||||
Document $project,
|
||||
Build $queueForBuilds,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Authorization $authorization
|
||||
) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
@@ -106,8 +107,8 @@ class Create extends Base
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Templates are always from GitHub
|
||||
$branchUrl = "https://github.com/$owner/$repository/blob/$reference";
|
||||
|
||||
$repositoryUrl = "https://github.com/$owner/$repository";
|
||||
|
||||
$template = new Document([
|
||||
@@ -120,6 +121,8 @@ class Create extends Base
|
||||
|
||||
if (!empty($function->getAttribute('providerRepositoryId'))) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId'));
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$deployment = $this->redeployVcsFunction(
|
||||
request: $request,
|
||||
@@ -129,7 +132,7 @@ class Create extends Base
|
||||
dbForProject: $dbForProject,
|
||||
queueForBuilds: $queueForBuilds,
|
||||
template: $template,
|
||||
github: $github,
|
||||
vcs: $vcs,
|
||||
activate: $activate,
|
||||
referenceType: $type,
|
||||
reference: $reference
|
||||
|
||||
@@ -16,10 +16,11 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class Create extends Base
|
||||
{
|
||||
@@ -71,7 +72,7 @@ class Create extends Base
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -87,7 +88,7 @@ class Create extends Base
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
Build $queueForBuilds,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -98,6 +99,8 @@ class Create extends Base
|
||||
$template = new Document();
|
||||
|
||||
$installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId'));
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$deployment = $this->redeployVcsFunction(
|
||||
request: $request,
|
||||
@@ -107,7 +110,7 @@ class Create extends Base
|
||||
dbForProject: $dbForProject,
|
||||
queueForBuilds: $queueForBuilds,
|
||||
template: $template,
|
||||
github: $github,
|
||||
vcs: $vcs,
|
||||
activate: $activate,
|
||||
reference: $reference,
|
||||
referenceType: $type
|
||||
|
||||
@@ -37,8 +37,9 @@ use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class Create extends Base
|
||||
{
|
||||
@@ -121,7 +122,7 @@ class Create extends Base
|
||||
->inject('queueForFunctions')
|
||||
->inject('dbForPlatform')
|
||||
->inject('request')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('authorization')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
@@ -163,7 +164,7 @@ class Create extends Base
|
||||
Func $queueForFunctions,
|
||||
Database $dbForPlatform,
|
||||
Request $request,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Authorization $authorization,
|
||||
array $platform
|
||||
) {
|
||||
@@ -287,6 +288,12 @@ class Create extends Base
|
||||
'providerPullRequestIds' => []
|
||||
]));
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $installation->getAttribute('providerInstallationId', ''), $providerRepositoryId);
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId);
|
||||
VcsFactory::createRepositoryWebhook($vcs, $provider, $owner, $repositoryName);
|
||||
|
||||
$function->setAttribute('repositoryId', $repository->getId());
|
||||
$function->setAttribute('repositoryInternalId', $repository->getSequence());
|
||||
}
|
||||
@@ -320,6 +327,8 @@ class Create extends Base
|
||||
$template = new Document();
|
||||
|
||||
$installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId'));
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$deployment = $this->redeployVcsFunction(
|
||||
request: $request,
|
||||
function: $function,
|
||||
@@ -328,7 +337,7 @@ class Create extends Base
|
||||
dbForProject: $dbForProject,
|
||||
queueForBuilds: $queueForBuilds,
|
||||
template: $template,
|
||||
github: $github,
|
||||
vcs: $vcs,
|
||||
activate: true,
|
||||
reference: $providerBranch,
|
||||
referenceType: 'branch'
|
||||
|
||||
@@ -33,7 +33,8 @@ use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
|
||||
class Update extends Base
|
||||
{
|
||||
@@ -107,7 +108,7 @@ class Update extends Base
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('dbForPlatform')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('executor')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
@@ -141,7 +142,7 @@ class Update extends Base
|
||||
Event $queueForEvents,
|
||||
Build $queueForBuilds,
|
||||
Database $dbForPlatform,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Executor $executor,
|
||||
Authorization $authorization
|
||||
) {
|
||||
@@ -219,6 +220,12 @@ class Update extends Base
|
||||
'providerPullRequestIds' => [],
|
||||
]));
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $installation->getAttribute('providerInstallationId', ''), $providerRepositoryId);
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId);
|
||||
VcsFactory::createRepositoryWebhook($vcs, $provider, $owner, $repositoryName);
|
||||
|
||||
$repositoryId = $repository->getId();
|
||||
$repositoryInternalId = $repository->getSequence();
|
||||
}
|
||||
@@ -285,7 +292,9 @@ class Update extends Base
|
||||
|
||||
// Redeploy logic
|
||||
if (!$isConnected && !empty($providerRepositoryId)) {
|
||||
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true);
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $vcs, true);
|
||||
}
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
|
||||
@@ -36,8 +36,9 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\System\System;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Adapter;
|
||||
|
||||
class Builds extends Action
|
||||
{
|
||||
@@ -121,7 +122,6 @@ class Builds extends Action
|
||||
case BUILD_TYPE_DEPLOYMENT:
|
||||
case BUILD_TYPE_RETRY:
|
||||
Console::info('Creating build for deployment: ' . $deployment->getId());
|
||||
$github = new GitHub($cache);
|
||||
$this->buildDeployment(
|
||||
$deviceForFunctions,
|
||||
$deviceForSites,
|
||||
@@ -135,7 +135,7 @@ class Builds extends Action
|
||||
$publisherForUsage,
|
||||
$dbForPlatform,
|
||||
$dbForProject,
|
||||
$github,
|
||||
$cache,
|
||||
$project,
|
||||
$resource,
|
||||
$deployment,
|
||||
@@ -170,7 +170,7 @@ class Builds extends Action
|
||||
UsagePublisher $publisherForUsage,
|
||||
Database $dbForPlatform,
|
||||
Database $dbForProject,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Document $project,
|
||||
Document $resource,
|
||||
Document $deployment,
|
||||
@@ -270,13 +270,15 @@ class Builds extends Action
|
||||
$owner = '';
|
||||
$repositoryName = '';
|
||||
|
||||
Console::log('[DEBUG build] deploymentId=' . $deploymentId . ' installationId=' . $installationId . ' providerRepositoryId=' . $providerRepositoryId . ' isVcsEnabled=' . ($isVcsEnabled ? 'true' : 'false') . ' source=' . $source);
|
||||
|
||||
$provider = 'github';
|
||||
$vcs = null;
|
||||
if ($isVcsEnabled) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -299,7 +301,9 @@ class Builds extends Action
|
||||
// Clone template repo
|
||||
$tmpTemplateDirectory = '/tmp/builds/' . $deploymentId . '-template';
|
||||
|
||||
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateReferenceValue, $templateReferenceType, $tmpTemplateDirectory, $templateRootDirectory);
|
||||
// Templates are public GitHub repos — no authentication needed
|
||||
$templateGitHub = VcsFactory::getAdapter('github', $cache);
|
||||
$gitCloneCommandForTemplate = $templateGitHub->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateReferenceValue, $templateReferenceType, $tmpTemplateDirectory, $templateRootDirectory);
|
||||
|
||||
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
|
||||
|
||||
@@ -357,8 +361,9 @@ class Builds extends Action
|
||||
$rootDirectory = \ltrim($rootDirectory, '.');
|
||||
$rootDirectory = \ltrim($rootDirectory, '/');
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId);
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId);
|
||||
|
||||
$cloneOwner = $deployment->getAttribute('providerRepositoryOwner', $owner);
|
||||
$cloneRepository = $deployment->getAttribute('providerRepositoryName', $repositoryName);
|
||||
@@ -367,13 +372,15 @@ class Builds extends Action
|
||||
$commitHash = $deployment->getAttribute('providerCommitHash', '');
|
||||
|
||||
$cloneVersion = $branchName;
|
||||
$cloneType = GitHub::CLONE_TYPE_BRANCH;
|
||||
$cloneType = Adapter::CLONE_TYPE_BRANCH;
|
||||
if (! empty($commitHash)) {
|
||||
$cloneVersion = $commitHash;
|
||||
$cloneType = GitHub::CLONE_TYPE_COMMIT;
|
||||
$cloneType = Adapter::CLONE_TYPE_COMMIT;
|
||||
}
|
||||
|
||||
$gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory);
|
||||
Console::log('[DEBUG build-vcs] provider=' . $provider . ' cloneOwner=' . $cloneOwner . ' cloneRepo=' . $cloneRepository . ' cloneVersion=' . $cloneVersion . ' cloneType=' . $cloneType . ' branch=' . $branchName . ' commitHash=' . $commitHash);
|
||||
|
||||
$gitCloneCommand = $vcs->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory);
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
@@ -387,12 +394,20 @@ class Builds extends Action
|
||||
|
||||
$exit = Console::execute($gitCloneCommand, '', $stdout, $stderr);
|
||||
|
||||
Console::log('[DEBUG build-vcs] clone exit=' . $exit . ' stdout=' . $stdout . ' stderr=' . $stderr);
|
||||
|
||||
if ($exit !== 0) {
|
||||
throw new \Exception('Unable to clone code repository: ' . $stderr);
|
||||
}
|
||||
|
||||
Console::log('Git repository cloned');
|
||||
|
||||
// List files to verify clone content
|
||||
$lsOut = '';
|
||||
$lsErr = '';
|
||||
Console::execute('ls -la ' . \escapeshellarg($tmpDirectory), '', $lsOut, $lsErr);
|
||||
Console::log('[DEBUG build-vcs] files in tmpDirectory: ' . $lsOut);
|
||||
|
||||
// Local refactoring for function folder with spaces
|
||||
if (str_contains($rootDirectory, ' ')) {
|
||||
$rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory);
|
||||
@@ -421,7 +436,9 @@ class Builds extends Action
|
||||
// Clone template repo
|
||||
$tmpTemplateDirectory = '/tmp/builds/' . $deploymentId . '/template';
|
||||
|
||||
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateReferenceValue, $templateReferenceType, $tmpTemplateDirectory, $templateRootDirectory);
|
||||
// Templates are public GitHub repos — no authentication needed
|
||||
$templateGitHub = VcsFactory::getAdapter('github', $cache);
|
||||
$gitCloneCommandForTemplate = $templateGitHub->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateReferenceValue, $templateReferenceType, $tmpTemplateDirectory, $templateRootDirectory);
|
||||
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
|
||||
|
||||
if ($exit !== 0) {
|
||||
@@ -435,8 +452,8 @@ class Builds extends Action
|
||||
// Merge template into user repo
|
||||
Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
|
||||
|
||||
// Commit and push
|
||||
$exit = Console::execute('git config --global user.email ' . \escapeshellarg(APP_VCS_GITHUB_EMAIL) . ' && git config --global user.name ' . \escapeshellarg(APP_VCS_GITHUB_USERNAME) . ' && cd ' . \escapeshellarg($tmpDirectory) . ' && git checkout -b ' . \escapeshellarg($branchName) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
|
||||
// Disable sparse-checkout so template files can be staged, then commit and push
|
||||
$exit = Console::execute('git config --global user.email ' . \escapeshellarg(APP_VCS_COMMIT_EMAIL) . ' && git config --global user.name ' . \escapeshellarg(APP_VCS_COMMIT_USERNAME) . ' && cd ' . \escapeshellarg($tmpDirectory) . ' && git sparse-checkout disable && git checkout -b ' . \escapeshellarg($branchName) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
|
||||
|
||||
if ($exit !== 0) {
|
||||
throw new \Exception('Unable to push code repository: ' . $stderr);
|
||||
@@ -451,10 +468,10 @@ class Builds extends Action
|
||||
$providerCommitHash = \trim($stdout);
|
||||
|
||||
$deployment->setAttribute('providerCommitHash', $providerCommitHash);
|
||||
$deployment->setAttribute('providerCommitAuthorUrl', APP_VCS_GITHUB_URL);
|
||||
$deployment->setAttribute('providerCommitAuthor', APP_VCS_GITHUB_USERNAME);
|
||||
$deployment->setAttribute('providerCommitAuthorUrl', VcsFactory::getProviderUrl($provider));
|
||||
$deployment->setAttribute('providerCommitAuthor', APP_VCS_COMMIT_USERNAME);
|
||||
$deployment->setAttribute('providerCommitMessage', "Create '" . $resource->getAttribute('name', '') . "' function");
|
||||
$deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash");
|
||||
$deployment->setAttribute('providerCommitUrl', VcsFactory::getCommitUrl($provider, $cloneOwner, $cloneRepository, $providerCommitHash));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'providerCommitHash' => $deployment->getAttribute('providerCommitHash'),
|
||||
'providerCommitAuthorUrl' => $deployment->getAttribute('providerCommitAuthorUrl'),
|
||||
@@ -521,7 +538,7 @@ class Builds extends Action
|
||||
|
||||
Console::log('Git source uploaded');
|
||||
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
$this->runGitAction('processing', $vcs, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
|
||||
Console::log('Status marked as building');
|
||||
@@ -541,7 +558,7 @@ class Builds extends Action
|
||||
->trigger();
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
$this->runGitAction('building', $vcs, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
|
||||
$deploymentModel = new Deployment();
|
||||
@@ -931,7 +948,7 @@ class Builds extends Action
|
||||
}
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
$this->runGitAction('ready', $vcs, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
|
||||
/** Set auto deploy */
|
||||
@@ -1183,7 +1200,7 @@ class Builds extends Action
|
||||
->trigger();
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
$this->runGitAction('failed', $vcs, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
} finally {
|
||||
$queueForRealtime
|
||||
@@ -1354,7 +1371,7 @@ class Builds extends Action
|
||||
*/
|
||||
protected function runGitAction(
|
||||
string $status,
|
||||
GitHub $github,
|
||||
?Adapter\Git $vcs,
|
||||
string $providerCommitHash,
|
||||
string $owner,
|
||||
string $repositoryName,
|
||||
@@ -1369,7 +1386,7 @@ class Builds extends Action
|
||||
$deployment = new Document();
|
||||
|
||||
try {
|
||||
if ($resource->getAttribute('providerSilentMode', false) === true) {
|
||||
if ($resource->getAttribute('providerSilentMode', false) === true || $vcs === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1408,7 +1425,7 @@ class Builds extends Action
|
||||
default => throw new \Exception('Invalid resource type')
|
||||
};
|
||||
|
||||
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name);
|
||||
$vcs->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name);
|
||||
}
|
||||
|
||||
if (! empty($commentId)) {
|
||||
@@ -1453,9 +1470,9 @@ class Builds extends Action
|
||||
};
|
||||
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
|
||||
$comment->parseComment($vcs->getComment($owner, $repositoryName, $commentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl);
|
||||
$github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
|
||||
$vcs->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
|
||||
} finally {
|
||||
$dbForPlatform->deleteDocument('vcsCommentLocks', $commentId);
|
||||
}
|
||||
|
||||
@@ -21,10 +21,11 @@ use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class Create extends Base
|
||||
{
|
||||
@@ -78,7 +79,7 @@ class Create extends Base
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('authorization')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
@@ -99,7 +100,7 @@ class Create extends Base
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
Build $queueForBuilds,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Authorization $authorization,
|
||||
array $platform
|
||||
) {
|
||||
@@ -109,6 +110,7 @@ class Create extends Base
|
||||
throw new Exception(Exception::SITE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Templates are always from GitHub
|
||||
$branchUrl = "https://github.com/$owner/$repository/blob/$reference";
|
||||
$repositoryUrl = "https://github.com/$owner/$repository";
|
||||
|
||||
@@ -122,6 +124,8 @@ class Create extends Base
|
||||
|
||||
if (!empty($site->getAttribute('providerRepositoryId'))) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $site->getAttribute('installationId'));
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$deployment = $this->redeployVcsSite(
|
||||
request: $request,
|
||||
@@ -132,7 +136,7 @@ class Create extends Base
|
||||
dbForPlatform: $dbForPlatform,
|
||||
queueForBuilds: $queueForBuilds,
|
||||
template: $template,
|
||||
github: $github,
|
||||
vcs: $vcs,
|
||||
activate: $activate,
|
||||
authorization: $authorization,
|
||||
platform: $platform
|
||||
|
||||
@@ -17,10 +17,11 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class Create extends Base
|
||||
{
|
||||
@@ -72,7 +73,7 @@ class Create extends Base
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('authorization')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
@@ -90,7 +91,7 @@ class Create extends Base
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
Build $queueForBuilds,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Authorization $authorization,
|
||||
array $platform
|
||||
) {
|
||||
@@ -103,6 +104,8 @@ class Create extends Base
|
||||
$template = new Document();
|
||||
|
||||
$installation = $dbForPlatform->getDocument('installations', $site->getAttribute('installationId'));
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$deployment = $this->redeployVcsSite(
|
||||
request: $request,
|
||||
@@ -113,7 +116,7 @@ class Create extends Base
|
||||
dbForPlatform: $dbForPlatform,
|
||||
queueForBuilds: $queueForBuilds,
|
||||
template: $template,
|
||||
github: $github,
|
||||
vcs: $vcs,
|
||||
activate: $activate,
|
||||
authorization: $authorization,
|
||||
reference: $reference,
|
||||
|
||||
@@ -19,6 +19,8 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -96,6 +98,7 @@ class Create extends Base
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('cache')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -125,7 +128,8 @@ class Create extends Base
|
||||
Database $dbForProject,
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
Database $dbForPlatform
|
||||
Database $dbForPlatform,
|
||||
Cache $cache
|
||||
) {
|
||||
if (!empty($adapter)) {
|
||||
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
|
||||
@@ -201,6 +205,13 @@ class Create extends Base
|
||||
'providerPullRequestIds' => []
|
||||
]);
|
||||
$repository = $dbForPlatform->createDocument('repositories', $repository);
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $installation->getAttribute('providerInstallationId', ''), $providerRepositoryId);
|
||||
$repoName = $vcs->getRepositoryName($providerRepositoryId);
|
||||
VcsFactory::createRepositoryWebhook($vcs, $provider, $owner, $repoName);
|
||||
|
||||
$site->setAttribute('repositoryId', $repository->getId());
|
||||
$site->setAttribute('repositoryInternalId', $repository->getSequence());
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
|
||||
class Update extends Base
|
||||
{
|
||||
@@ -101,7 +102,7 @@ class Update extends Base
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('dbForPlatform')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('executor')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -135,7 +136,7 @@ class Update extends Base
|
||||
Event $queueForEvents,
|
||||
Build $queueForBuilds,
|
||||
Database $dbForPlatform,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Executor $executor
|
||||
) {
|
||||
if (!empty($adapter)) {
|
||||
@@ -215,6 +216,13 @@ class Update extends Base
|
||||
'providerPullRequestIds' => []
|
||||
]);
|
||||
$repository = $dbForPlatform->createDocument('repositories', $repository);
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $installation->getAttribute('providerInstallationId', ''), $providerRepositoryId);
|
||||
$repoName = $vcs->getRepositoryName($providerRepositoryId);
|
||||
VcsFactory::createRepositoryWebhook($vcs, $provider, $owner, $repoName);
|
||||
|
||||
$repositoryId = $repository->getId();
|
||||
$repositoryInternalId = $repository->getSequence();
|
||||
}
|
||||
@@ -283,7 +291,9 @@ class Update extends Base
|
||||
|
||||
// Redeploy logic
|
||||
if (!$isConnected && !empty($providerRepositoryId)) {
|
||||
$this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true);
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $vcs, true);
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('siteId', $site->getId());
|
||||
|
||||
+19
-19
@@ -10,14 +10,14 @@ use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
class Update extends Action
|
||||
@@ -34,7 +34,8 @@ class Update extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/repositories/:repositoryId')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
|
||||
->desc('Update external deployment (authorize)')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
@@ -53,8 +54,8 @@ class Update extends Action
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('repositoryId', '', new Text(256), 'VCS Repository Id')
|
||||
->param('providerPullRequestId', '', new Text(256), 'GitHub Pull Request Id')
|
||||
->inject('gitHub')
|
||||
->param('providerPullRequestId', '', new Text(256), 'Pull Request Id')
|
||||
->inject('cache')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
@@ -69,7 +70,7 @@ class Update extends Action
|
||||
string $installationId,
|
||||
string $repositoryId,
|
||||
string $providerPullRequestId,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
@@ -101,36 +102,35 @@ class Update extends Action
|
||||
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds])));
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$repositories = [$repository];
|
||||
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
|
||||
|
||||
try {
|
||||
$providerRepositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
$providerRepositoryName = $vcs->getRepositoryName($providerRepositoryId);
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$pullRequestResponse = $github->getPullRequest($owner, $providerRepositoryName, $providerPullRequestId);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId);
|
||||
$pullRequestResponse = $vcs->getPullRequest($owner, $providerRepositoryName, $providerPullRequestId);
|
||||
|
||||
$providerRepositoryUrl = $pullRequestResponse['head']['repo']['html_url'] ?? '';
|
||||
$providerRepositoryOwner = $pullRequestResponse['head']['repo']['owner']['login'] ?? '';
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'] ?? '')[1] ?? ($pullRequestResponse['head']['ref'] ?? '');
|
||||
$providerBranchUrl = "$providerRepositoryUrl/tree/$providerBranch";
|
||||
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
|
||||
|
||||
$commitDetails = $github->getCommit($providerRepositoryOwner, $providerRepositoryName, $providerCommitHash);
|
||||
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
|
||||
$providerCommitUrl = $commitDetails["commitUrl"] ?? '';
|
||||
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
|
||||
$providerCommitAuthorUrl = $commitDetails["commitAuthorUrl"] ?? '';
|
||||
$commitDetails = $vcs->getCommit($providerRepositoryOwner, $providerRepositoryName, $providerCommitHash);
|
||||
$providerCommitMessage = $commitDetails['commitMessage'] ?? '';
|
||||
$providerCommitUrl = $commitDetails['commitUrl'] ?? '';
|
||||
$providerCommitAuthor = $commitDetails['commitAuthor'] ?? '';
|
||||
$providerCommitAuthorUrl = $commitDetails['commitAuthorUrl'] ?? '';
|
||||
|
||||
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
|
||||
$this->createGitDeployments($vcs, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Adapter\Git;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
trait Deployment
|
||||
{
|
||||
protected function createGitDeployments(
|
||||
GitHub $github,
|
||||
Git $github,
|
||||
string $providerInstallationId,
|
||||
array $repositories,
|
||||
string $providerBranch,
|
||||
@@ -107,7 +107,7 @@ trait Deployment
|
||||
$activate = true;
|
||||
}
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$owner = $providerRepositoryOwner;
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
} catch (RepositoryNotFound $e) {
|
||||
@@ -290,7 +290,7 @@ trait Deployment
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$owner = $providerRepositoryOwner;
|
||||
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $authorizeUrl, $name);
|
||||
continue;
|
||||
}
|
||||
@@ -520,7 +520,7 @@ trait Deployment
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$owner = $providerRepositoryOwner;
|
||||
|
||||
$providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
|
||||
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $providerTargetUrl, $name);
|
||||
|
||||
@@ -163,7 +163,7 @@ class Create extends Action
|
||||
]));
|
||||
|
||||
// Create new deployment only on push (not committed by us) and not when branch is deleted
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchDeleted) {
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_COMMIT_EMAIL && !$providerBranchDeleted) {
|
||||
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\VCS\Http\Gitea\Authorize;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getVCSGiteaAuthorize';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vcs/gitea/authorize')
|
||||
->desc('Create Gitea installation')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('error', APP_VIEWS_DIR . '/general/error.phtml')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'vcs',
|
||||
group: 'installations',
|
||||
name: 'createGiteaInstallation',
|
||||
description: '/docs/references/vcs/create-gitea-installation.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_MOVED_PERMANENTLY,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::HTML,
|
||||
type: MethodType::WEBAUTH,
|
||||
hide: true,
|
||||
))
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to console after a successful installation attempt.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to console after a failed installation attempt.', true, ['redirectValidator'])
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $success,
|
||||
string $failure,
|
||||
Response $response,
|
||||
Document $project,
|
||||
array $platform
|
||||
) {
|
||||
$endpoint = VcsFactory::getEndpoint('gitea');
|
||||
$browserEndpoint = VcsFactory::getBrowserEndpoint('gitea');
|
||||
$clientId = VcsFactory::getClientId('gitea');
|
||||
|
||||
if (empty($endpoint) || empty($clientId)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Gitea VCS is not configured. Please configure _APP_VCS_GITEA_ENDPOINT and _APP_VCS_GITEA_CLIENT_ID environment variables.');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$state = \json_encode([
|
||||
'projectId' => $project->getId(),
|
||||
'success' => $success,
|
||||
'failure' => $failure,
|
||||
]);
|
||||
|
||||
$url = rtrim($browserEndpoint, '/') . '/login/oauth/authorize?' . \http_build_query([
|
||||
'client_id' => $clientId,
|
||||
'redirect_uri' => $protocol . '://' . $hostname . '/v1/vcs/gitea/callback',
|
||||
'response_type' => 'code',
|
||||
'scope' => 'read:user write:repository read:organization',
|
||||
'state' => $state,
|
||||
]);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\VCS\Http\Gitea\Callback;
|
||||
|
||||
use Appwrite\Auth\OAuth2\Gitea as OAuth2Gitea;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Permission as AppwritePermission;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
use AppwritePermission;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getVCSGiteaCallback';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vcs/gitea/callback')
|
||||
->desc('Get installation and authorization from Gitea')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->label('error', APP_VIEWS_DIR . '/general/error.phtml')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code.', true)
|
||||
->param('state', '', new Text(2048), 'State containing project info.', true)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $code,
|
||||
string $state,
|
||||
Document $project,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
array $platform
|
||||
) {
|
||||
if (empty($state)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Missing state parameter.');
|
||||
}
|
||||
|
||||
$state = \json_decode($state, true);
|
||||
$redirectFailure = $state['failure'] ?? '';
|
||||
$projectId = $state['projectId'] ?? '';
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$error = 'Project with the ID from state could not be found.';
|
||||
|
||||
if (!empty($redirectFailure)) {
|
||||
$separator = \str_contains($redirectFailure, '?') ? '&' : ':';
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($redirectFailure . $separator . \http_build_query(['error' => $error]));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
|
||||
}
|
||||
|
||||
$region = $project->getAttribute('region', 'default');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$defaultState = [
|
||||
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
|
||||
'failure' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
|
||||
];
|
||||
|
||||
$state = \array_merge($defaultState, $state ?? []);
|
||||
$redirectSuccess = $state['success'] ?? '';
|
||||
$redirectFailure = $state['failure'] ?? '';
|
||||
|
||||
if (empty($code)) {
|
||||
$error = 'OAuth2 authorization code is missing.';
|
||||
|
||||
if (!empty($redirectFailure)) {
|
||||
$separator = \str_contains($redirectFailure, '?') ? '&' : ':';
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($redirectFailure . $separator . \http_build_query(['error' => $error]));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
|
||||
}
|
||||
|
||||
$endpoint = VcsFactory::getEndpoint('gitea');
|
||||
$clientId = VcsFactory::getClientId('gitea');
|
||||
$clientSecret = VcsFactory::getClientSecret('gitea');
|
||||
|
||||
$oauth2 = new OAuth2Gitea($clientId, $clientSecret, '', [], [], $endpoint);
|
||||
|
||||
$accessToken = $oauth2->getAccessToken($code) ?? '';
|
||||
$refreshToken = $oauth2->getRefreshToken($code) ?? '';
|
||||
$accessTokenExpiry = DateTime::addSeconds(new \DateTime(), \intval($oauth2->getAccessTokenExpiry($code)));
|
||||
|
||||
$owner = $oauth2->getUserSlug($accessToken) ?? '';
|
||||
|
||||
if (empty($owner)) {
|
||||
$error = 'Failed to get user information from Gitea.';
|
||||
|
||||
if (!empty($redirectFailure)) {
|
||||
$separator = \str_contains($redirectFailure, '?') ? '&' : ':';
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($redirectFailure . $separator . \http_build_query(['error' => $error]));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
|
||||
}
|
||||
|
||||
$projectInternalId = $project->getSequence();
|
||||
|
||||
// Use the OAuth2 user ID as the providerInstallationId for Gitea
|
||||
$providerInstallationId = $oauth2->getUserID($accessToken);
|
||||
|
||||
$installation = $dbForPlatform->findOne('installations', [
|
||||
Query::equal('providerInstallationId', [$providerInstallationId]),
|
||||
Query::equal('projectInternalId', [$projectInternalId]),
|
||||
Query::equal('provider', ['gitea']),
|
||||
]);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$installation = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => $this->getPermissions($teamId, $projectId),
|
||||
'providerInstallationId' => $providerInstallationId,
|
||||
'projectId' => $projectId,
|
||||
'projectInternalId' => $projectInternalId,
|
||||
'provider' => 'gitea',
|
||||
'organization' => $owner,
|
||||
'personal' => true,
|
||||
'personalRefreshToken' => $refreshToken,
|
||||
'personalAccessToken' => $accessToken,
|
||||
'personalAccessTokenExpiry' => $accessTokenExpiry,
|
||||
]);
|
||||
|
||||
$installation = $dbForPlatform->createDocument('installations', $installation);
|
||||
} else {
|
||||
$installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
|
||||
'organization' => $owner,
|
||||
'personal' => true,
|
||||
'personalRefreshToken' => $refreshToken,
|
||||
'personalAccessToken' => $accessToken,
|
||||
'personalAccessTokenExpiry' => $accessTokenExpiry,
|
||||
]));
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($redirectSuccess);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\VCS\Http\Gitea\Events;
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\Platform\Modules\VCS\Http\GitHub\Deployment;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\VCS\Adapter\Git\Gitea;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
use Deployment;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createVCSGiteaEvent';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/vcs/gitea/events')
|
||||
->desc('Create Gitea event')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->inject('cache')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
Cache $cache,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
callable $getProjectDB,
|
||||
Build $queueForBuilds,
|
||||
array $platform
|
||||
) {
|
||||
$event = $request->getHeader('x-gitea-event', '');
|
||||
Span::add('vcs.gitea.event.name', $event);
|
||||
|
||||
$payload = $request->getRawPayload();
|
||||
$signature = $request->getHeader('x-gitea-signature', '');
|
||||
$secretKey = VcsFactory::getWebhookSecret('gitea');
|
||||
|
||||
$gitea = VcsFactory::getAdapter('gitea', $cache);
|
||||
|
||||
$valid = empty($secretKey) ? true : $gitea->validateWebhookEvent($payload, $signature, $secretKey);
|
||||
Span::add('vcs.gitea.event.signature.valid', $valid);
|
||||
|
||||
if (!$valid) {
|
||||
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Invalid webhook payload signature.');
|
||||
}
|
||||
|
||||
$parsedPayload = $gitea->getEvent($event, $payload);
|
||||
|
||||
match ($event) {
|
||||
'push' => $this->handlePushEvent($parsedPayload, $gitea, $cache, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform),
|
||||
'pull_request' => $this->handlePullRequestEvent($parsedPayload, $gitea, $cache, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform),
|
||||
default => null,
|
||||
};
|
||||
|
||||
$response->json($parsedPayload);
|
||||
}
|
||||
|
||||
private function handlePushEvent(
|
||||
array $parsedPayload,
|
||||
Gitea $gitea,
|
||||
Cache $cache,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Build $queueForBuilds,
|
||||
callable $getProjectDB,
|
||||
array $platform,
|
||||
) {
|
||||
$providerBranchDeleted = $parsedPayload['branchDeleted'] ?? false;
|
||||
$providerBranch = $parsedPayload['branch'] ?? '';
|
||||
$providerBranchUrl = $parsedPayload['branchUrl'] ?? '';
|
||||
$providerRepositoryId = $parsedPayload['repositoryId'] ?? '';
|
||||
$providerRepositoryName = $parsedPayload['repositoryName'] ?? '';
|
||||
$providerRepositoryUrl = $parsedPayload['repositoryUrl'] ?? '';
|
||||
$providerCommitHash = $parsedPayload['commitHash'] ?? '';
|
||||
$providerRepositoryOwner = $parsedPayload['owner'] ?? '';
|
||||
$providerCommitAuthorName = $parsedPayload['headCommitAuthorName'] ?? '';
|
||||
$providerCommitAuthorEmail = $parsedPayload['headCommitAuthorEmail'] ?? '';
|
||||
$providerCommitAuthorUrl = $parsedPayload['authorUrl'] ?? '';
|
||||
$providerCommitMessage = $parsedPayload['headCommitMessage'] ?? '';
|
||||
$providerCommitUrl = $parsedPayload['headCommitUrl'] ?? '';
|
||||
|
||||
Span::add('vcs.gitea.event.repo.id', $providerRepositoryId);
|
||||
Span::add('vcs.gitea.event.repo.name', $providerRepositoryName);
|
||||
Span::add('vcs.gitea.event.branch', $providerBranch);
|
||||
|
||||
// Find the installation by looking up the repository's organization and provider
|
||||
$installation = $this->findInstallation($providerRepositoryOwner, $dbForPlatform, $authorization);
|
||||
if ($installation === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$vcs = VcsFactory::getInitializedAdapter('gitea', $installation, $cache);
|
||||
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::limit(100),
|
||||
]));
|
||||
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_COMMIT_EMAIL && !$providerBranchDeleted) {
|
||||
$this->createGitDeployments($vcs, '', $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
|
||||
}
|
||||
}
|
||||
|
||||
private function handlePullRequestEvent(
|
||||
array $parsedPayload,
|
||||
Gitea $gitea,
|
||||
Cache $cache,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Build $queueForBuilds,
|
||||
callable $getProjectDB,
|
||||
array $platform,
|
||||
) {
|
||||
$action = $parsedPayload['action'] ?? '';
|
||||
|
||||
if ($action === 'opened' || $action === 'reopened' || $action === 'synchronize' || $action === 'synchronized') {
|
||||
$providerBranch = $parsedPayload['branch'] ?? '';
|
||||
$providerBranchUrl = $parsedPayload['branchUrl'] ?? '';
|
||||
$providerRepositoryId = $parsedPayload['repositoryId'] ?? '';
|
||||
$providerRepositoryName = $parsedPayload['repositoryName'] ?? '';
|
||||
$providerRepositoryUrl = $parsedPayload['repositoryUrl'] ?? '';
|
||||
$providerPullRequestId = $parsedPayload['pullRequestNumber'] ?? '';
|
||||
$providerCommitHash = $parsedPayload['commitHash'] ?? '';
|
||||
$providerRepositoryOwner = $parsedPayload['owner'] ?? '';
|
||||
$external = $parsedPayload['external'] ?? true;
|
||||
$providerCommitUrl = $parsedPayload['headCommitUrl'] ?? '';
|
||||
$providerCommitAuthorUrl = $parsedPayload['authorUrl'] ?? '';
|
||||
|
||||
Span::add('vcs.gitea.event.repo.id', $providerRepositoryId);
|
||||
Span::add('vcs.gitea.event.repo.name', $providerRepositoryName);
|
||||
Span::add('vcs.gitea.event.branch', $providerBranch);
|
||||
|
||||
// Ignore sync for non-external
|
||||
if (!$external && ($action === 'synchronize' || $action === 'synchronized')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$installation = $this->findInstallation($providerRepositoryOwner, $dbForPlatform, $authorization);
|
||||
if ($installation === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$vcs = VcsFactory::getInitializedAdapter('gitea', $installation, $cache);
|
||||
|
||||
$commitDetails = $vcs->getCommit($providerRepositoryOwner, $providerRepositoryName, $providerCommitHash);
|
||||
$providerCommitAuthor = $commitDetails['commitAuthor'] ?? '';
|
||||
$providerCommitMessage = $commitDetails['commitMessage'] ?? '';
|
||||
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$this->createGitDeployments($vcs, '', $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $platform);
|
||||
} elseif ($action === 'closed') {
|
||||
$providerRepositoryId = $parsedPayload['repositoryId'] ?? '';
|
||||
$providerPullRequestId = $parsedPayload['pullRequestNumber'] ?? '';
|
||||
$external = $parsedPayload['external'] ?? true;
|
||||
|
||||
if ($external) {
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$providerPullRequestIds = $repository->getAttribute('providerPullRequestIds', []);
|
||||
|
||||
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
|
||||
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an installation by organization name for Gitea provider.
|
||||
*/
|
||||
private function findInstallation(string $organization, Database $dbForPlatform, Authorization $authorization): ?Document
|
||||
{
|
||||
$installation = $authorization->skip(fn () => $dbForPlatform->findOne('installations', [
|
||||
Query::equal('organization', [$organization]),
|
||||
Query::equal('provider', ['gitea']),
|
||||
]));
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installation;
|
||||
}
|
||||
}
|
||||
+13
-11
@@ -8,12 +8,12 @@ use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
class XList extends Action
|
||||
@@ -29,7 +29,8 @@ class XList extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
|
||||
->desc('List repository branches')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -49,7 +50,7 @@ class XList extends Action
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
@@ -58,7 +59,7 @@ class XList extends Action
|
||||
public function action(
|
||||
string $installationId,
|
||||
string $providerRepositoryId,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
@@ -68,14 +69,15 @@ class XList extends Action
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$accessToken = $installation->getAttribute('personalAccessToken', '');
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId) ?? '';
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -83,7 +85,7 @@ class XList extends Action
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$branches = $github->listBranches($owner, $repositoryName) ?? [];
|
||||
$branches = $vcs->listBranches($owner, $repositoryName) ?? [];
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'branches' => \array_map(function ($branch) {
|
||||
|
||||
+14
-11
@@ -8,12 +8,14 @@ use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Adapter\Git\Gitea;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
class Get extends Action
|
||||
@@ -29,7 +31,8 @@ class Get extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
|
||||
->desc('Get files and directories of a VCS repository')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -51,7 +54,7 @@ class Get extends Action
|
||||
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
|
||||
->param('providerRootDirectory', '', new Text(256, 0), 'Path to get contents of nested directory', true)
|
||||
->param('providerReference', '', new Text(256, 0), 'Git reference (branch, tag, commit) to get contents from', true)
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
@@ -62,7 +65,7 @@ class Get extends Action
|
||||
string $providerRepositoryId,
|
||||
string $providerRootDirectory,
|
||||
string $providerReference,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
@@ -72,14 +75,13 @@ class Get extends Action
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -87,12 +89,13 @@ class Get extends Action
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory, $providerReference);
|
||||
$contents = $vcs->listRepositoryContents($owner, $repositoryName, $providerRootDirectory, $providerReference);
|
||||
|
||||
$contentsDir = ($vcs instanceof GitHub) ? GitHub::CONTENTS_DIRECTORY : Gitea::CONTENTS_DIRECTORY;
|
||||
$vcsContents = [];
|
||||
foreach ($contents as $content) {
|
||||
$isDirectory = false;
|
||||
if ($content['type'] === GitHub::CONTENTS_DIRECTORY) {
|
||||
if ($content['type'] === $contentsDir) {
|
||||
$isDirectory = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\VCS\Http\Installations\Repositories;
|
||||
|
||||
use Appwrite\Auth\OAuth2\Gitea as OAuth2Gitea;
|
||||
use Appwrite\Auth\OAuth2\Github as OAuth2Github;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
@@ -9,15 +10,15 @@ use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
@@ -32,7 +33,8 @@ class Create extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/providerRepositories')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->desc('Create repository')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
@@ -53,7 +55,7 @@ class Create extends Action
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('name', '', new Text(256), 'Repository name (slug)')
|
||||
->param('private', '', new Boolean(false), 'Mark repository public or private')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
@@ -64,7 +66,7 @@ class Create extends Action
|
||||
string $installationId,
|
||||
string $name,
|
||||
bool $private,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Document $user,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
@@ -75,28 +77,38 @@ class Create extends Action
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
|
||||
if ($installation->getAttribute('personal', false) === true) {
|
||||
$oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), "");
|
||||
$clientId = VcsFactory::getClientId($provider);
|
||||
$clientSecret = VcsFactory::getClientSecret($provider);
|
||||
|
||||
$accessToken = $installation->getAttribute('personalAccessToken');
|
||||
$refreshToken = $installation->getAttribute('personalRefreshToken');
|
||||
$accessTokenExpiry = $installation->getAttribute('personalAccessTokenExpiry');
|
||||
|
||||
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
|
||||
$identity = $dbForPlatform->findOne('identities', [
|
||||
Query::equal('provider', ['github']),
|
||||
Query::equal('userInternalId', [$user->getSequence()]),
|
||||
]);
|
||||
if ($identity->isEmpty()) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
if ($provider === 'github') {
|
||||
$oauth2 = new OAuth2Github($clientId, $clientSecret, '');
|
||||
|
||||
$accessToken = $accessToken ?? $identity->getAttribute('providerAccessToken');
|
||||
$refreshToken = $refreshToken ?? $identity->getAttribute('providerRefreshToken');
|
||||
$accessTokenExpiry = $accessTokenExpiry ?? $identity->getAttribute('providerAccessTokenExpiry');
|
||||
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
|
||||
$identity = $dbForPlatform->findOne('identities', [
|
||||
Query::equal('provider', ['github']),
|
||||
Query::equal('userInternalId', [$user->getSequence()]),
|
||||
]);
|
||||
if ($identity->isEmpty()) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$accessToken = $accessToken ?? $identity->getAttribute('providerAccessToken');
|
||||
$refreshToken = $refreshToken ?? $identity->getAttribute('providerRefreshToken');
|
||||
$accessTokenExpiry = $accessTokenExpiry ?? $identity->getAttribute('providerAccessTokenExpiry');
|
||||
}
|
||||
} else {
|
||||
$endpoint = VcsFactory::getEndpoint($provider);
|
||||
$oauth2 = new OAuth2Gitea($clientId, $clientSecret, '', [], [], $endpoint);
|
||||
}
|
||||
|
||||
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
|
||||
$isExpired = !empty($accessTokenExpiry) && new \DateTime($accessTokenExpiry) < new \DateTime('now');
|
||||
if ($isExpired) {
|
||||
$oauth2->refreshTokens($refreshToken);
|
||||
|
||||
@@ -106,13 +118,13 @@ class Create extends Action
|
||||
$verificationId = $oauth2->getUserID($accessToken);
|
||||
|
||||
if (empty($verificationId)) {
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, "Another request is currently refreshing OAuth token. Please try again.");
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.');
|
||||
}
|
||||
|
||||
$installation = $installation
|
||||
->setAttribute('personalAccessToken', $accessToken)
|
||||
->setAttribute('personalRefreshToken', $refreshToken)
|
||||
->setAttribute('personalAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
->setAttribute('personalAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
|
||||
'personalAccessToken' => $installation->getAttribute('personalAccessToken'),
|
||||
@@ -124,19 +136,17 @@ class Create extends Action
|
||||
try {
|
||||
$repository = $oauth2->createRepository($accessToken, $name, $private);
|
||||
} catch (Exception $exception) {
|
||||
throw new Exception(Exception::GENERAL_PROVIDER_FAILURE, "GitHub failed to process the request: " . $exception->getMessage());
|
||||
throw new Exception(Exception::GENERAL_PROVIDER_FAILURE, "Provider failed to process the request: " . $exception->getMessage());
|
||||
}
|
||||
} else {
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId);
|
||||
|
||||
try {
|
||||
$repository = $github->createRepository($owner, $name, $private);
|
||||
$repository = $vcs->createRepository($owner, $name, $private);
|
||||
} catch (Exception $exception) {
|
||||
throw new Exception(Exception::GENERAL_PROVIDER_FAILURE, "GitHub failed to process the request: " . $exception->getMessage());
|
||||
throw new Exception(Exception::GENERAL_PROVIDER_FAILURE, "Provider failed to process the request: " . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-14
@@ -46,11 +46,11 @@ use Utopia\Detector\Detector\Framework;
|
||||
use Utopia\Detector\Detector\Packager;
|
||||
use Utopia\Detector\Detector\Runtime;
|
||||
use Utopia\Detector\Detector\Strategy;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\FileNotFound;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
@@ -67,8 +67,10 @@ class Create extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/detections')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/detections')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/detections')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
->httpAlias('/v1/vcs/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
->desc('Create repository detection')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
@@ -93,7 +95,7 @@ class Create extends Action
|
||||
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
|
||||
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
|
||||
->param('providerRootDirectory', '', new Text(256, 0), 'Path to Root Directory', true)
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
@@ -104,7 +106,7 @@ class Create extends Action
|
||||
string $providerRepositoryId,
|
||||
string $type,
|
||||
string $providerRootDirectory,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
@@ -114,14 +116,13 @@ class Create extends Action
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -129,9 +130,9 @@ class Create extends Action
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$files = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
|
||||
$files = $vcs->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
|
||||
$files = \array_column($files, 'name');
|
||||
$languages = $github->listRepositoryLanguages($owner, $repositoryName);
|
||||
$languages = $vcs->listRepositoryLanguages($owner, $repositoryName);
|
||||
|
||||
$detector = new Packager();
|
||||
foreach ($files as $file) {
|
||||
@@ -148,7 +149,7 @@ class Create extends Action
|
||||
if ($type === 'framework') {
|
||||
$packages = '';
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/package.json');
|
||||
$contentResponse = $vcs->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/package.json');
|
||||
$packages = $contentResponse['content'] ?? '';
|
||||
} catch (FileNotFound $e) {
|
||||
// Continue detection without package.json
|
||||
@@ -280,7 +281,7 @@ class Create extends Action
|
||||
$wg->add();
|
||||
go(function () use ($github, $owner, $repositoryName, $providerRootDirectory, $file, $wg, &$envs) {
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/' . $file);
|
||||
$contentResponse = $vcs->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/' . $file);
|
||||
$envFile = $contentResponse['content'] ?? '';
|
||||
|
||||
$configAdapter = new ConfigDotenv();
|
||||
|
||||
@@ -8,10 +8,11 @@ use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
@@ -29,7 +30,8 @@ class Get extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/providerRepositories/:providerRepositoryId')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId')
|
||||
->desc('Get repository')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -49,7 +51,7 @@ class Get extends Action
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
@@ -58,7 +60,7 @@ class Get extends Action
|
||||
public function action(
|
||||
string $installationId,
|
||||
string $providerRepositoryId,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
@@ -68,14 +70,13 @@ class Get extends Action
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$owner = VcsFactory::getOwnerName($vcs, $provider, $providerInstallationId, $providerRepositoryId) ?? '';
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
$repositoryName = $vcs->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -83,12 +84,12 @@ class Get extends Action
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = $github->getRepository($owner, $repositoryName);
|
||||
$repository = $vcs->getRepository($owner, $repositoryName);
|
||||
|
||||
$authorized = $github->hasAccessToAllRepositories();
|
||||
if (!$authorized) {
|
||||
$authorized = $vcs->hasAccessToAllRepositories();
|
||||
if (!$authorized && $vcs instanceof GitHub) {
|
||||
try {
|
||||
$installationRepository = $github->getInstallationRepository($repositoryName);
|
||||
$installationRepository = $vcs->getInstallationRepository($repositoryName);
|
||||
if (!empty($installationRepository)) {
|
||||
$authorized = true;
|
||||
}
|
||||
@@ -100,8 +101,8 @@ class Get extends Action
|
||||
$repository['id'] = \strval($repository['id']) ?? '';
|
||||
$repository['pushedAt'] = $repository['pushed_at'] ?? '';
|
||||
$repository['organization'] = $installation->getAttribute('organization', '');
|
||||
$repository['provider'] = $installation->getAttribute('provider', '');
|
||||
$repository['defaultBranch'] = $repository['default_branch'] ?? '';
|
||||
$repository['provider'] = $provider;
|
||||
$repository['defaultBranch'] = $repository['default_branch'] ?? '';
|
||||
$repository['authorized'] = $authorized;
|
||||
$repository['providerInstallationId'] = $providerInstallationId;
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ use Utopia\Detector\Detector\Framework;
|
||||
use Utopia\Detector\Detector\Packager;
|
||||
use Utopia\Detector\Detector\Runtime;
|
||||
use Utopia\Detector\Detector\Strategy;
|
||||
use Appwrite\Vcs\VcsFactory;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\FileNotFound;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
@@ -72,7 +72,8 @@ class XList extends Action
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->setHttpPath('/v1/vcs/installations/:installationId/providerRepositories')
|
||||
->httpAlias('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->desc('List repositories')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
@@ -97,7 +98,7 @@ class XList extends Action
|
||||
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('gitHub')
|
||||
->inject('cache')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
@@ -108,7 +109,7 @@ class XList extends Action
|
||||
string $type,
|
||||
string $search,
|
||||
array $queries,
|
||||
GitHub $github,
|
||||
Cache $cache,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
@@ -122,10 +123,9 @@ class XList extends Action
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$provider = $installation->getAttribute('provider', 'github');
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$vcs = VcsFactory::getInitializedAdapter($provider, $installation, $cache);
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$limitQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_LIMIT));
|
||||
@@ -139,8 +139,8 @@ class XList extends Action
|
||||
}
|
||||
|
||||
$page = ($offset / $limit) + 1;
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
['items' => $repos, 'total' => $total] = $github->searchRepositories($owner, $page, $limit, $search);
|
||||
$owner = $installation->getAttribute('organization', '');
|
||||
['items' => $repos, 'total' => $total] = $vcs->searchRepositories($owner, $page, $limit, $search);
|
||||
|
||||
$repos = \array_map(function ($repo) use ($installation) {
|
||||
$repo['id'] = \strval($repo['id'] ?? '');
|
||||
@@ -152,9 +152,9 @@ class XList extends Action
|
||||
return $repo;
|
||||
}, $repos);
|
||||
|
||||
$repos = batch(\array_map(function ($repo) use ($type, $github) {
|
||||
return function () use ($repo, $type, $github) {
|
||||
$files = $github->listRepositoryContents($repo['organization'], $repo['name'], '');
|
||||
$repos = batch(\array_map(function ($repo) use ($type, $vcs) {
|
||||
return function () use ($repo, $type, $vcs) {
|
||||
$files = $vcs->listRepositoryContents($repo['organization'], $repo['name'], '');
|
||||
$files = \array_column($files, 'name');
|
||||
|
||||
$detector = new Packager();
|
||||
@@ -172,7 +172,7 @@ class XList extends Action
|
||||
if ($type === 'framework') {
|
||||
$packages = '';
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], 'package.json');
|
||||
$contentResponse = $vcs->getRepositoryContent($repo['organization'], $repo['name'], 'package.json');
|
||||
$packages = $contentResponse['content'] ?? '';
|
||||
} catch (FileNotFound $e) {
|
||||
// Continue detection without package.json
|
||||
@@ -214,7 +214,7 @@ class XList extends Action
|
||||
}
|
||||
$repo['framework'] = $framework;
|
||||
} else {
|
||||
$languages = $github->listRepositoryLanguages($repo['organization'], $repo['name']);
|
||||
$languages = $vcs->listRepositoryLanguages($repo['organization'], $repo['name']);
|
||||
|
||||
$strategies = [
|
||||
new Strategy(Strategy::FILEMATCH),
|
||||
@@ -275,9 +275,9 @@ class XList extends Action
|
||||
}
|
||||
|
||||
$wg->add();
|
||||
go(function () use ($github, $repo, $file, $wg, &$envs) {
|
||||
go(function () use ($vcs, $repo, $file, $wg, &$envs) {
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], $file);
|
||||
$contentResponse = $vcs->getRepositoryContent($repo['organization'], $repo['name'], $file);
|
||||
$envFile = $contentResponse['content'] ?? '';
|
||||
|
||||
$configAdapter = new ConfigDotenv();
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\VCS\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\VCS\Http\Gitea\Authorize\Get as GetGiteaAuthorize;
|
||||
use Appwrite\Platform\Modules\VCS\Http\Gitea\Callback\Get as GetGiteaCallback;
|
||||
use Appwrite\Platform\Modules\VCS\Http\Gitea\Events\Create as CreateGiteaEvent;
|
||||
use Appwrite\Platform\Modules\VCS\Http\GitHub\Authorize\External\Update as UpdateExternalDeployment;
|
||||
use Appwrite\Platform\Modules\VCS\Http\GitHub\Authorize\Get as GetGitHubAuthorize;
|
||||
use Appwrite\Platform\Modules\VCS\Http\GitHub\Callback\Get as GetGitHubCallback;
|
||||
@@ -26,6 +29,12 @@ class Http extends Service
|
||||
// GitHub Authorization & Callback
|
||||
$this->addAction(GetGitHubAuthorize::getName(), new GetGitHubAuthorize());
|
||||
$this->addAction(GetGitHubCallback::getName(), new GetGitHubCallback());
|
||||
|
||||
// Gitea Authorization & Callback
|
||||
$this->addAction(GetGiteaAuthorize::getName(), new GetGiteaAuthorize());
|
||||
$this->addAction(GetGiteaCallback::getName(), new GetGiteaCallback());
|
||||
|
||||
// External Deployments
|
||||
$this->addAction(UpdateExternalDeployment::getName(), new UpdateExternalDeployment());
|
||||
|
||||
// Installations
|
||||
@@ -41,7 +50,8 @@ class Http extends Service
|
||||
$this->addAction(GetRepositoryContents::getName(), new GetRepositoryContents());
|
||||
$this->addAction(CreateRepositoryDetections::getName(), new CreateRepositoryDetections());
|
||||
|
||||
// Events
|
||||
// Events (per-provider webhook handlers)
|
||||
$this->addAction(CreateGitHubEvent::getName(), new CreateGitHubEvent());
|
||||
$this->addAction(CreateGiteaEvent::getName(), new CreateGiteaEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Vcs;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\System\System;
|
||||
use Utopia\VCS\Adapter\Git;
|
||||
use Utopia\VCS\Adapter\Git\Gitea;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
||||
class VcsFactory
|
||||
{
|
||||
/**
|
||||
* Create a VCS adapter instance for the given provider.
|
||||
*/
|
||||
public static function getAdapter(string $provider, Cache $cache): Git
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => new GitHub($cache),
|
||||
'gitea' => self::createGiteaBasedAdapter($provider, $cache),
|
||||
default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, "Unsupported VCS provider: {$provider}"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a VCS adapter with credentials from the installation document and environment.
|
||||
*/
|
||||
public static function initializeAdapter(Git $adapter, string $provider, Document $installation): void
|
||||
{
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
|
||||
|
||||
match ($provider) {
|
||||
'github' => $adapter->initializeVariables(
|
||||
$providerInstallationId,
|
||||
System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''),
|
||||
System::getEnv('_APP_VCS_GITHUB_APP_ID', ''),
|
||||
),
|
||||
'gitea' => $adapter->initializeVariables(
|
||||
'',
|
||||
'',
|
||||
null,
|
||||
$installation->getAttribute('personalAccessToken', ''),
|
||||
$installation->getAttribute('personalRefreshToken', ''),
|
||||
),
|
||||
default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, "Unsupported VCS provider: {$provider}"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and initialize a VCS adapter in one call.
|
||||
*/
|
||||
public static function getInitializedAdapter(string $provider, Document $installation, Cache $cache): Git
|
||||
{
|
||||
$adapter = self::getAdapter($provider, $cache);
|
||||
self::initializeAdapter($adapter, $provider, $installation);
|
||||
return $adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the browser-facing base URL for a VCS provider.
|
||||
* Used for constructing user-visible URLs (repo, branch, commit links).
|
||||
*/
|
||||
public static function getProviderUrl(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => 'https://github.com',
|
||||
'gitea' => rtrim(self::getBrowserEndpoint($provider), '/'),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a branch URL for the given provider.
|
||||
*/
|
||||
public static function getBranchUrl(string $provider, string $owner, string $repo, string $branch): string
|
||||
{
|
||||
$base = self::getProviderUrl($provider);
|
||||
if (empty($base)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return match ($provider) {
|
||||
'github' => "{$base}/{$owner}/{$repo}/tree/{$branch}",
|
||||
'gitea' => "{$base}/{$owner}/{$repo}/src/branch/{$branch}",
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a repository URL for the given provider.
|
||||
*/
|
||||
public static function getRepoUrl(string $provider, string $owner, string $repo): string
|
||||
{
|
||||
$base = self::getProviderUrl($provider);
|
||||
if (empty($base)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return "{$base}/{$owner}/{$repo}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a commit URL for the given provider.
|
||||
*/
|
||||
public static function getCommitUrl(string $provider, string $owner, string $repo, string $hash): string
|
||||
{
|
||||
$base = self::getProviderUrl($provider);
|
||||
if (empty($base)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return match ($provider) {
|
||||
'github' => "{$base}/{$owner}/{$repo}/commit/{$hash}",
|
||||
'gitea' => "{$base}/{$owner}/{$repo}/commit/{$hash}",
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the webhook secret env var for a provider.
|
||||
*/
|
||||
public static function getWebhookSecret(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', ''),
|
||||
'gitea' => System::getEnv('_APP_VCS_GITEA_WEBHOOK_SECRET', ''),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OAuth2 client ID env var for a provider.
|
||||
*/
|
||||
public static function getClientId(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''),
|
||||
'gitea' => System::getEnv('_APP_VCS_GITEA_CLIENT_ID', ''),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OAuth2 client secret env var for a provider.
|
||||
*/
|
||||
public static function getClientSecret(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''),
|
||||
'gitea' => System::getEnv('_APP_VCS_GITEA_CLIENT_SECRET', ''),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint URL for a self-hosted provider.
|
||||
*/
|
||||
public static function getEndpoint(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'gitea' => System::getEnv('_APP_VCS_GITEA_ENDPOINT', ''),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the browser-facing endpoint URL for a self-hosted provider.
|
||||
*
|
||||
* In Docker, the internal endpoint (e.g. http://gitea:3000) differs from
|
||||
* the URL the browser can reach (e.g. http://localhost:9510). This returns
|
||||
* the browser-facing URL, falling back to the internal endpoint if not set.
|
||||
*/
|
||||
public static function getBrowserEndpoint(string $provider): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'gitea' => System::getEnv('_APP_VCS_GITEA_BROWSER_ENDPOINT', System::getEnv('_APP_VCS_GITEA_ENDPOINT', '')),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the owner name from a VCS adapter, handling provider differences.
|
||||
*
|
||||
* GitHub uses installationId, Gitea-based providers require repositoryId.
|
||||
*/
|
||||
public static function getOwnerName(Git $adapter, string $provider, string $providerInstallationId, ?string $providerRepositoryId = null): string
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => $adapter->getOwnerName($providerInstallationId),
|
||||
'gitea' => $adapter->getOwnerName('', !empty($providerRepositoryId) ? intval($providerRepositoryId) : null),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provider is configured via environment variables.
|
||||
*/
|
||||
public static function isProviderConfigured(string $provider): bool
|
||||
{
|
||||
return match ($provider) {
|
||||
'github' => !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', '')),
|
||||
'gitea' => !empty(System::getEnv('_APP_VCS_GITEA_ENDPOINT', '')),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configured VCS providers.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getConfiguredProviders(): array
|
||||
{
|
||||
$configured = [];
|
||||
foreach (APP_VCS_PROVIDERS as $provider) {
|
||||
if (self::isProviderConfigured($provider)) {
|
||||
$configured[] = $provider;
|
||||
}
|
||||
}
|
||||
return $configured;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a webhook on a repository for non-GitHub providers.
|
||||
*
|
||||
* GitHub manages webhooks via its App — no per-repo setup needed.
|
||||
* Gitea (and similar) require explicit webhook creation on each repository.
|
||||
*/
|
||||
public static function createRepositoryWebhook(Git $adapter, string $provider, string $owner, string $repositoryName): void
|
||||
{
|
||||
if ($provider === 'github') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the internal webhook URL if set (for Docker-to-Docker communication),
|
||||
// otherwise fall back to the public domain.
|
||||
$internalUrl = System::getEnv('_APP_VCS_WEBHOOK_URL', '');
|
||||
if (empty($internalUrl)) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http://' : 'https://';
|
||||
$internalUrl = $protocol . System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
}
|
||||
|
||||
$webhookUrl = rtrim($internalUrl, '/') . "/v1/vcs/{$provider}/events";
|
||||
$webhookSecret = self::getWebhookSecret($provider);
|
||||
|
||||
$adapter->createWebhook($owner, $repositoryName, $webhookUrl, $webhookSecret, ['push', 'pull_request']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Gitea-based adapter with the correct endpoint.
|
||||
*/
|
||||
private static function createGiteaBasedAdapter(string $provider, Cache $cache): Gitea
|
||||
{
|
||||
$adapter = new Gitea($cache);
|
||||
$endpoint = self::getEndpoint($provider);
|
||||
if (!empty($endpoint)) {
|
||||
$adapter->setEndpoint($endpoint);
|
||||
}
|
||||
return $adapter;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user