GitHub Actions بعمق

سير العمل القابلة لإعادة الاستخدام والإجراءات المركّبة

22 دقيقة الدرس 6 من 30

سير العمل القابلة لإعادة الاستخدام والإجراءات المركّبة

مع نمو المنصة، تتكرر نفس أنماط CI/CD عبر عشرات المستودعات: الفحص، الاختبار، بناء صورة Docker، الرفع إلى السجل، والنشر على بيئة التجربة. نسخ ملفات YAML للسير العمل هو أسرع طريق إلى فوضى يصعب صيانتها — فتطبيق تصحيح أمني أو ترقية Runner يستلزم التعديل في 40 مكانًا. يوفر GitHub Actions آليتين متكاملتين للخروج من هذه الدوامة: سير العمل القابلة لإعادة الاستخدام والإجراءات المركّبة. معرفة أي أداة تناسب أي مشكلة، وكيفية إصدار الاثنتين بشكل صحيح، هو ما يميز منصة CI احترافية عن مجموعة من ملفات YAML المتكررة.

سير العمل القابلة لإعادة الاستخدام — workflow_call

سير العمل القابل لإعادة الاستخدام هو ملف سير عمل عادي يُعلن on: workflow_call بوصفه أحد مُشغّلاته. يمكن لأي سير عمل آخر استدعاؤه كأنه مهمة واحدة — ينفّذ الملف المستدعى بعزلة كاملة مع مهامه ورنّراته وبيئاته الخاصة. هذه هي الأداة الصحيحة عندما تريد مشاركة مراحل خط أنابيب كاملة تشمل منطق تعدد المهام وبوابات البيئة وبيانات OIDC.

تدعم سير العمل القابلة لإعادة الاستخدام مُدخلات مكتوبة (نص، منطقي، رقمي، اختيار) وأسرارًا تُمرَّر، ويمكنها إصدار مخرجات يستهلكها المُستدعي. تُعلن المُدخلات تحت on.workflow_call.inputs، والأسرار تحت on.workflow_call.secrets.

# .github/workflows/reusable-docker-build.yml (في المستودع ذاته أو مستودع مشترك) name: Reusable — Docker Build & Push on: workflow_call: inputs: image_name: description: "اسم صورة Docker (بدون بادئة السجل)" required: true type: string tag: description: "وسم الصورة (مثال: git SHA أو semver)" required: false default: "latest" type: string push: description: "رفع الصورة إلى السجل بعد البناء" required: false default: true type: boolean secrets: registry_token: required: true outputs: image_digest: description: "ملخص SHA256 للصورة المرفوعة" value: ${{ jobs.build.outputs.digest }} jobs: build: runs-on: ubuntu-24.04 outputs: digest: ${{ steps.push.outputs.digest }} steps: - uses: actions/checkout@v4 - name: إعداد Docker Buildx uses: docker/setup-buildx-action@v3 - name: تسجيل الدخول إلى GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.registry_token }} - name: البناء والرفع id: push uses: docker/build-push-action@v5 with: context: . push: ${{ inputs.push }} tags: ghcr.io/${{ github.repository_owner }}/${{ inputs.image_name }}:${{ inputs.tag }} cache-from: type=gha cache-to: type=gha,mode=max

يستدعي مُستدعٍ في أي مستودع داخل المنظمة هذا السير عمل باستخدام مفتاح uses على مستوى المهمة — وليس مستوى الخطوة:

# .github/workflows/ci.yml (في أي مستودع مستهلك) name: CI on: push: branches: [main] jobs: test: uses: my-org/.github/.github/workflows/reusable-docker-build.yml@v2 with: image_name: api-service tag: ${{ github.sha }} push: true secrets: registry_token: ${{ secrets.GHCR_TOKEN }} deploy: needs: test runs-on: ubuntu-24.04 steps: - name: استخدام ملخص الصورة من خطوة البناء run: echo "Deploying ${{ needs.test.outputs.image_digest }}"
يقبل مفتاح uses على مستوى المهمة ثلاثة أشكال للمرجع: org/repo/.github/workflows/file.yml@ref للمستودعات المتقاطعة، و./.github/workflows/file.yml للمستودع ذاته (بدون @ref)، ومستودع org/.github الخاص كمخزن مشترك على مستوى المنظمة. دائمًا اربط استدعاءات المستودعات المتقاطعة بوسم أو SHA — لا تستخدم فرعًا متغيرًا مثل @main في الإنتاج؛ فأي تغيير كاسر سيُصيب جميع المُستدعين فورًا.

الإجراءات المركّبة

الإجراء المركّب هو تسلسل خطوات قابل لإعادة الاستخدام مُعبَّأ في ملف action.yml. على خلاف سير العمل القابلة لإعادة الاستخدام، تعمل الإجراءات المركّبة كـخطوات داخل مهمة قائمة — تشترك في الرنّر ومساحة العمل وجميع متغيرات البيئة الخاصة بالمهمة المُستدعِية. هذا يجعلها التجريد الصحيح لتغليف تسلسل خطوات قابل للتكرار (تثبيت التبعيات، تهيئة أداة، تشغيل فاحص) دون تكلفة مهمة منفصلة.

أنشئ الإجراء في مستودع تحت أي مسار (بالاصطلاح .github/actions/<name>/action.yml للإجراءات داخل المستودع ذاته، أو الجذر action.yml لمستودع إجراء مستقل).

# .github/actions/setup-node-pnpm/action.yml name: "Setup Node + pnpm" description: "يثبّت إصدار Node الصحيح، يُفعّل pnpm عبر corepack، ويستعيد تخزين pnpm المؤقت." inputs: node-version: description: "إصدار Node.js للتثبيت" required: false default: "20" pnpm-version: description: "إصدار pnpm للتفعيل عبر corepack" required: false default: "9" outputs: cache-hit: description: "هل تم استعادة التخزين المؤقت لـ pnpm" value: ${{ steps.cache.outputs.cache-hit }} runs: using: "composite" steps: - name: إعداد Node ${{ inputs.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - name: تفعيل pnpm ${{ inputs.pnpm-version }} عبر corepack shell: bash run: | corepack enable corepack prepare pnpm@${{ inputs.pnpm-version }} --activate - name: استعادة التخزين المؤقت لـ pnpm id: cache uses: actions/cache@v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ inputs.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | pnpm-${{ runner.os }}-${{ inputs.node-version }}- - name: تثبيت التبعيات shell: bash run: pnpm install --frozen-lockfile

كل خطوة run داخل الإجراء المركّب يجب أن تُعلن shell — لا يستطيع GitHub استنتاجها من السياق كما يفعل في سير العمل العادي. نسيان هذا هو الخطأ الأكثر شيوعًا عند كتابة الإجراءات.

تعمل الإجراءات المركّبة داخل المهمة المُستدعِية وبالتالي ترث أسرارها تلقائيًا — لا تمرر الأسرار عبر المُدخلات. غير أن هذا يعني أيضًا أن إجراءً مركّبًا مخترقًا يستطيع قراءة جميع الأسرار على الرنّر. طبّق نفس نموذج الثقة الذي تستخدمه مع إجراءات الطرف الثالث: ربط الإصدار بـ SHA كامل، ومراجعة التغييرات في مراجعة الكود، ولا تستخدم وسمًا متغيرًا في خط أنابيب حساس أمنيًا.

معمارية — متى تستخدم أيًا منهما

Reusable Workflow vs Composite Action decision diagram Need separate jobs / env gates? YES Reusable Workflow NO Composite Action Reusable Workflow • يعمل في مهام منفصلة • رنّرات معزولة • بوابات البيئة والموافقة • إدخال أسرار مكتوبة • مشاركة عبر المستودعات • إخراج للمُستدعي Composite Action • خطوات داخل مهمة • يشترك في الرنّر والمساحة • يرث البيئة والأسرار • خفيف وسريع • action.yml في أي مستودع • لا وحدة فوترة منفصلة
دليل اتخاذ القرار: اختر سير العمل القابل لإعادة الاستخدام لعزل المهام وبوابات البيئة؛ اختر الإجراء المركّب لتغليف الخطوات داخل مهمة.

معايير المنظمة الشاملة مع مستودع سير عمل مركزي

تُخزّن المنظمات الهندسية الكبيرة جميع سير العمل القابلة لإعادة الاستخدام والإجراءات المركّبة الرسمية في مستودع مشترك واحد — بالاصطلاح يُسمى .github تحت المنظمة (مثال: my-org/.github). يمنح GitHub هذا المستودع قوتين خاصتين: مجلد .github/workflows/ هو مسار البحث الافتراضي لمراجع workflow_call بالشكل المختصر، ومجلد workflow-templates/ يملأ واجهة "Actions" لإنشاء سير عمل جديد لكل مستودع في المنظمة.

اقرن المستودع المشترك بـسير العمل المطلوبة (يُهيَّأ في إعدادات المنظمة ← Actions ← Required workflows). يعمل سير العمل المطلوب على كل طلب سحب في كل مستودع في المنظمة — بغض النظر عما تفعله CI الخاصة بالمستودع. هكذا تُطبّق فرق المنصة عمليات الفحص الأمني وفحص الترخيص أو توليد SBOM دون مطالبة كل فريق تطوير بالاشتراك يدويًا.

قم بإصدار سير العمل المشتركة بوسوم Git دلالية (v1، v1.2، v1.2.3). يربط المُستدعون بـ@v1 (وسم خفيف تحركه للأمام مع إصدارات الترقيع)، مما يمنحك إمكانية دفع تحسينات غير كاسرة دون أن يحتاج المُستدعون لتحديث ملفات YAML الخاصة بهم. قدّم التغييرات الكاسرة تحت @v2 وحافظ على الاثنين بالتوازي خلال فترة ترحيل — نفس النموذج الذي يستخدمه actions/checkout وactions/setup-node.

أشكال الفشل الشائعة

  • غياب shell في خطوات run المركّبة — يفشل الإجراء برسالة خطأ غامضة. كل خطوة run داخل runs.using: composite تحتاج shell: bash (أو pwsh أو python إلخ).
  • تمرير الأسرار كمُدخلات للإجراءات المركّبة — الأسرار تُورث تلقائيًا؛ تمريرها كمُدخلات يعرّض قيمها في سجل ملخص سير العمل. مرّر الأسرار فقط إلى سير العمل القابلة لإعادة الاستخدام عبر on.workflow_call.secrets.
  • الاستدعاء الدائري — سير عمل قابل لإعادة الاستخدام يستدعي نفسه (مباشرةً أو عبر سلسلة) هو خطأ صارم. يكتشف GitHub الحلقات ويمنعها حتى ثلاثة مستويات عمق.
  • حد العمق — يمكن تداخل سير العمل القابلة لإعادة الاستخدام حتى أربعة مستويات عمق. الوصول إلى هذا الحد عادةً ما يشير إلى أن التجريد دقيق جدًا؛ قم بدمجه.
  • مرجع متغير في استدعاء مستودع متقاطع@main على المُستدعي يعني أن أي دفع قسري أو إيداع عرضي في المستودع المشترك يُصيب جميع المُستدعين فورًا. استخدم دائمًا وسمًا أو SHA.