أتمتة التحقق من طلبات السحب (Pull Requests) في AWS لتسهيل مراجعات الكود
Pull Requests). قد ينجم ذلك عن دمج طلب سحب قبل آخر، أو تقدم الفرع المستهدف (destination branch) بعدة التزامات (commits) مما يؤدي إلى تعارضات، أو ربما لأن مطوراً لم يقم بتشغيل الاختبارات قبل الدفع، فقام بإدخال خطأ غير مقصود في جزء آخر من المنتج. والقائمة تطول.لا ينبغي أن تكون هذه المشكلات عائقاً كبيراً، فكل مؤسسة لديها سير عمل (
workflow) لمراجعات الكود (Code Reviews)، أليس كذلك؟ ومع ذلك، فإن هذه المراجعات تستغرق وقتاً طويلاً، خاصةً لطلبات السحب التي تحتوي على أخطاء وليست جاهزة للمراجعة. يمكننا بالطبع بناء واختبار الكود يدوياً في كل مرة قبل مراجعة الكود الفعلية، ولكن بعد نقطة معينة، يصبح الأتمتة هي الحل الأمثل.تخيل مؤسسة متوسطة الحجم تتعامل مع 100-150 طلب سحب أسبوعياً. إن الوقت الذي يُهدر في التحقق المتكرر من هذه الطلبات يمكن أن يُستثمر في تطوير مجموعة كاملة من الميزات الجديدة للشركة. إذاً، دعونا نستكشف كيف يمكننا تحقيق هذه الميزات من خلال الأتمتة!
المتطلبات الأساسية
لتحقيق أقصى استفادة من هذا الدليل، يجب أن تكون لديك دراية بخدمات AWS. نفترض أنك تعرف كيفية إنشاء وإدارة دوال Lambda Functions، ومشاريع CodeBuild Projects، وأحداث CloudWatch Events، وأدوار IAM Roles، وأنك تستخدم CodeCommit لإدارة إصدارات الكود الخاص بك.
فهم البنية المعمارية للحل
دعونا نفهم، على مستوى عالٍ، كيف سنتعامل مع هذا المشروع لضمان التحقق الفعال من طلبات السحب.
آلية سير العمل خطوة بخطوة
لنستعرض سير العمل بمزيد من التفصيل:
- عند إنشاء طلب سحب جديد (
Pull Request) أو تحديث طلب سحب موجود في مستودع الكود الخاص بك (Code Repository). - يتم تفعيل حدث
CloudWatch Eventالذي يراقب المستودع، ويرسل البيانات ذات الصلة إلى دالةLambda. - تقوم دالة
Lambdaهذه بمهمتين رئيسيتين:- تشغيل مشروع
CodeBuild Projectلبناء أحدث التزام (commit) وتشغيل الاختبارات. - إضافة تعليق مخصص (
custom message) على طلب السحب الخاص بك.
- تشغيل مشروع
- بعد انتهاء
CodeBuildمن عملية البناء والاختبار، يتم تفعيل حدثCloudWatch Eventآخر يرسل نتائج البناء إلى دالةLambdaثانية. - تقوم هذه الدالة بإضافة تعليق على طلب السحب يتضمن نتائج البناء.
الآن بعد أن فهمنا الصورة الكبيرة، دعنا نبدأ في التنفيذ!
إعداد تطبيقنا النموذجي
لتبسيط الشرح، قمت بإنشاء تطبيق Node.js بسيط مكتوب بلغة TypeScript. كل ما تفعله مرحلة البناء (build phase) هو تجميع الملف app.ts إلى app.js. يمكنك العثور على رابط المستودع (repo) هنا – قم باستنساخه (clone) واستخدامه إذا أردت متابعة الخطوات. جميع الأكواد ذات الصلة المستخدمة في هذه المقالة موجودة هناك.
أمر البناء (build command) المستخدم هنا هو ببساطة tsc app.ts، ولكن يمكنك تعديله ليتناسب مع أمر البناء الخاص بمشروعك. وللحفاظ على البساطة، لم أقم بتضمين حالات اختبار (test cases). يمكنك ربطها بـ test في قسم scripts ضمن ملف package.json ومتابعة الشرح.
إعداد مشروع CodeBuild
الخطوة الأولى هي إعداد مشروع CodeBuild أساسي لمستودع الكود الخاص بك. للقيام بذلك، اتبع الإرشادات التالية:
- قم بتعيين المصدر (
source) ليكون مستودعCodeCommitالخاص بك. - يجب أن يكون نوع المرجع (
Reference type) هوbranch. - يجب أن تكون البيئة (
Environment) متوافقة مع متطلبات مشروعك. - يجب عليك استخدام ملف
buildspec. - بقية الإعدادات يمكن أن تكون افتراضية.
تأكد من وجود ملف buildspec.yml في المجلد الجذر لمستودعك.
ملاحظة هامة: قد يختلف هذا الإعداد إذا كنت تتعامل مع مستودع أحادي (MonoRepo). في هذه الحالة، قد يكون لديك ملفات buildspec.yml منفصلة لكل تطبيق، وسيتعين عليك تمرير مسار ملف buildspec بشكل انتقائي كمتغير بيئة (environment variable) بناءً على الملفات التي تم تغييرها داخل الالتزام (commit). لدينا إعداد مشابه في مؤسستنا، ونحن راضون جداً عن النتائج حتى الآن!
version: 0.2
phases:
install:
commands:
- n 12.12
- curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
- echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
- apt update
- apt install yarn
- yarn install
# pre_build:
# commands:
# - yarn test
build:
commands:
- yarn build
شرح ملف buildspec.yml
يقوم ملف buildspec.yml بتمرير أوامر وقت التشغيل (runtime commands) لكل عملية بناء إلى مشروع CodeBuild الخاص بنا. وإليك ما يفعله بالتحديد:
- يثبت
Node.jsبالإصدار12.12.0. - يثبت
Yarn. - يثبت تبعيات المشروع (
project’s dependencies). yarn test: (يقوم بتشغيل حالات الاختبار الخاصة بنا. لا توجد حالات اختبار هنا، ولكن يمكنك إلغاء التعليق على هذا القسم إذا كنت بحاجة إليه).yarn build: (يقوم ببناء مشروعنا).
دوال Lambda
سنقوم بإعداد دالتين من دوال Lambda كما ناقشنا في قسم البنية المعمارية أعلاه:
- دالة
TriggerCodebuildStart: ستتلقى هذه الدالة حدثCloudWatch Event(الذي سنقوم بإعداده لاحقاً) وستقوم بتشغيل مشروعCodeBuildالخاص بنا لبدء عملية بناء جديدة. كما أنها ستنشر تعليقاً “Build Started” يتضمن الطابع الزمني (timestamp) ورابطاً مباشراً لسجلات البناء (build logs) في قسم التعليقات الخاص بطلب السحب (PR). - دالة
TriggerCodebuildResult: ستتلقى هذه الدالة حدثCloudWatch Eventمن مشروعCodeBuildالخاص بنا، والذي سيحتوي على نتائج البناء. وستقوم أيضاً بنشر تعليق “CodeBuild Results” يتضمن الطابع الزمني ورابطاً مباشراً لسجلات البناء في قسم التعليقات الخاص بطلب السحب.
إليكم الكود الخاص بالدالتين، هذا ما كنتم تنتظرونه، أليس كذلك!
const AWS = require ( 'aws-sdk' );
const codecommit = new AWS.CodeCommit();
const codebuild = new AWS.CodeBuild();
exports .handler = async (event) => {
try {
console .log( 'Received Event: ' , event);
const { destinationCommit } = event.detail;
const { sourceCommit } = event.detail;
const { pullRequestId } = event.detail;
const pullRequestName = event.detail.title;
const sourceBranch = event.detail.sourceReference.split( '/' ).pop();
const triggerCodeBuildParameters = {
sourceBranch,
sourceCommit,
destinationCommit,
pullRequestId,
pullRequestName
};
const codeBuildResult = await triggerCodebuild(triggerCodeBuildParameters);
const buildId = codeBuildResult.build.id;
const postBuildStartedCommentOnPRParameters = {
sourceCommit,
destinationCommit,
pullRequestId,
buildId
}
await postBuildStartedCommentOnPR(postBuildStartedCommentOnPRParameters);
return { statusCode : 200 };
} catch (error) {
console .log( 'An Error Occured' , error);
return { error };
}
};
async function postBuildStartedCommentOnPR ( postBuildStartedCommentOnPRParameters ) {
const { sourceCommit, destinationCommit, pullRequestId, buildId } = postBuildStartedCommentOnPRParameters;
const logLink = `https:// ${process.env.REGION} .console.aws.amazon.com/codesuite/codebuild/projects/ValidatePullRequest/build/ ${buildId} ` ;
const parameters = {
afterCommitId : sourceCommit,
beforeCommitId : destinationCommit,
content : `Build For Validating The Pull Request has been started. Timestamp: ** ${ Date .now()} ** Check [CodeBuild Logs]( ${logLink} )` ,
pullRequestId,
repositoryName : process.env.REPOSITORY_NAME
};
const request = await codecommit.postCommentForPullRequest(parameters);
const promise = request.promise();
return promise.then(
( data ) => data,
( error ) => {
console .log( 'Error In Commenting To Pull Request' , error);
throw new Error (error);
}
);
}
async function triggerCodebuild ( triggerCodeBuildParameters ) {
const { sourceBranch, sourceCommit, destinationCommit, pullRequestId, pullRequestName } = triggerCodeBuildParameters;
console .log( `Triggering Codebuild, Branch: ${sourceBranch} ` );
const parameters = {
projectName : process.CODEBUILD_PROJECT,
sourceVersion : `refs/heads/ ${sourceBranch} ^{ ${sourceCommit} }` ,
environmentVariablesOverride : [
{
name : 'pullRequestId' ,
value : pullRequestId,
type : 'PLAINTEXT'
},
{
name : 'sourceCommit' ,
value : sourceCommit,
type : 'PLAINTEXT'
},
{
name : 'destinationCommit' ,
value : destinationCommit,
type : 'PLAINTEXT'
},
{
name : 'pullRequestName' ,
value : pullRequestName,
type : 'PLAINTEXT'
}
]
};
const request = await codebuild.startBuild(parameters);
const promise = request.promise();
return promise.then(
( data ) => data,
( error ) => {
console .log( 'Error In Starting Codebuild' , error);
throw new Error (error);
}
);
}
const AWS = require ( 'aws-sdk' );
const codecommit = new AWS.CodeCommit();
exports .handler = async (event) => {
try {
console .log( 'Event' , event);
const parameters = await getParameters(event);
console .log( 'Parameters For Comment:' , parameters);
await commentCodeBuildResultOnPR(parameters);
return { statusCode : 200 };
} catch (error) {
console .log( 'An Error Occured' , error);
return { error };
}
};
async function getParameters ( event ) {
try {
const buildId = event.detail[ 'build-id' ].split( '/' )[ 1 ];
const buildStatus = event.detail[ 'build-status' ];
const environmentVariableList = event.detail[ 'additional-information' ].environment[ 'environment-variables' ];
let afterCommitId, beforeCommitId, content, pullRequestId;
for (element of environmentVariableList) {
if (element.name === 'pullRequestId' ) pullRequestId = element.value;
if (element.name === 'sourceCommit' ) afterCommitId = element.value;
if (element.name === 'destinationCommit' ) beforeCommitId = element.value;
if (element.name === 'pullRequestName' ) pullRequestName = element.value;
}
const logLink = `https:// ${process.env.REGION} .console.aws.amazon.com/codesuite/codebuild/projects/ValidatePullRequest/build/ ${buildId} ` ;
content = `Build Result: ** ${buildStatus} ** Timestamp: ** ${ Date .now()} ** Check [CodeBuild Logs]( ${logLink} )` ;
return { afterCommitId, beforeCommitId, content, pullRequestId, repositoryName : process.env.REPOSITORY_NAME };
} catch (error) {
throw error;
}
}
async function commentCodeBuildResultOnPR ( parameters ) {
const request = await codecommit.postCommentForPullRequest(parameters);
const promise = request.promise();
return promise.then(
( data ) => data,
( error ) => {
console .log( 'Error In Commenting To Pull Request' , error);
throw new Error (error);
}
);
}
ستحتاج إلى ملء متغيرات البيئة (environment variables) المناسبة قبل استخدام هذه الدوال. بقراءة الكود مرة واحدة، ستعرف ما يجب فعله. في حال احتجت إلى الرجوع إلى الوثائق، يمكنك زيارة هذه الروابط هنا و هناك.
تكوين أحداث CloudWatch
لربط جميع المكونات معاً، سنقوم الآن بتكوين أحداث CloudWatch Events. سننشئ حدثين رئيسيين:
- حدث لاستقبال بيانات الالتزامات الجديدة (
new commit data) من مستودع الكود الخاص بنا. - حدث آخر لاستقبال نتائج
CodeBuild.
ستكون الأهداف (targets) لهذه الأحداث هي دوال Lambda الخاصة بنا. سأرفق لقطات شاشة كاملة للصفحات لتسهيل فهم المراجع.
ركز على الأحداث المظللة باللون الأخضر.
تأكد من استبدال ARN الخاص بمشروع CodeBuild الخاص بك هنا.
لقد اخترت تشغيل دالة Lambda عند أحداث FAILED (فشل) و SUCCEEDED (نجاح). ولكن يمكنك أيضاً تحديد “All Events” (جميع الأحداث) وتخصيصها لتناسب احتياجاتك. والآن، حان وقت التنفيذ!
المشاهدة العملية: التنفيذ!
إذا وصلت إلى هذه النقطة، فأنت رائع حقاً! بعد كل هذا العمل، دعنا نرى ما حققناه. سنقوم بإنشاء طلبي سحب (Pull Requests): أحدهما يعمل بشكل صحيح، والآخر يحتوي على خطأ بناء (build error) متعمد.
طلب سحب خالٍ من الأخطاء


ممتاز! لقد تم البناء بنجاح.
طلب سحب مع أخطاء متعمدة
الآن، دعنا ننشئ طلب سحب يحتوي على أخطاء. لاحظ هنا، بدلاً من app.get، كتبنا ap.get. إنه خطأ متعمد وبسيط، لكنه سيؤدي الغرض المطلوب حالياً.

رسالة بناء فاشلة، ومراجعون سعداء! لم يضطروا إلى سحب الفرع (checkout the branch) واختباره يدوياً.
أيها المطورون، كالعادة، السجلات متاحة لكم لمراجعة الأخطاء!
خاتمة وتطلعات مستقبلية
للارتقاء بهذا الحل خطوة إلى الأمام، يمكنك دمج إشعارات فورية. على سبيل المثال، يمكن تشغيل استدعاء API إلى رابط webhook URL الخاص بـ Slack لإرسال تنبيه فوري إلى قناة مخصصة في حالة فشل البناء (build failures). أليس هذا رائعاً؟
تجدر الإشارة إلى أن هذا الإعداد بسيط نسبياً، وقد تكون المشاريع الواقعية أكثر تعقيداً. فمثلاً، قد تحتوي المستودعات الأحادية (MonoRepo) على تطبيقات متعددة، وتختلف عمليات البناء والاختبار لكل تطبيق منها. في هذه الحالات، لن يكون تشغيل جميع الاختبارات في كل مرة أمراً فعالاً، بل سيكون مكلفاً ويسبب ارتباكاً. قد تحتاج إلى تشغيل عمليات البناء والاختبار بشكل انتقائي بناءً على الملفات التي تم الالتزام بها (committed files) والتطبيقات المتأثرة.
ومع ذلك، توفر هذه المقالة أساساً متيناً يمكنك البناء عليه وتوسيعه بلا شك. شكراً لقراءتكم! إذا احتجت إلى أي مساعدة بخصوص هذا الموضوع، فلا تتردد في التواصل معي عبر LinkedIn. أتطلع إلى تقديم المساعدة بكل الطرق الممكنة.
الخلاصة التقنية
تُظهر هذه المقالة بوضوح كيف يمكن لأتمتة التحقق من طلبات السحب في AWS أن تُحدث ثورة في سير عمل مراجعة الكود. من خلال الاستفادة من قوة AWS Lambda لتنسيق المهام، و AWS CodeBuild لتنفيذ عمليات البناء والاختبار، و Amazon CloudWatch Events كآلية تشغيل، يمكن للفرق تقليل التدخل اليدوي بشكل كبير. هذا لا يقتصر فقط على توفير الوقت الثمين للمطورين والمراجعين، بل يضمن أيضاً جودة كود أعلى عن طريق اكتشاف الأخطاء مبكراً في دورة التطوير. إن القدرة على تقديم ملاحظات فورية على طلبات السحب تعزز ثقافة التطوير المستمر (Continuous Development) وتسرع من وتيرة التسليم، مما يجعلها استراتيجية لا غنى عنها لأي فريق تطوير حديث يسعى للتميز في بيئة سحابية.