انشر تطبيقات الويب تلقائيًا باستخدام GitHub Webhooks

نشرت: 2022-03-11

أي شخص يطور تطبيقات الويب ويحاول تشغيلها على خوادمه غير المُدارة يكون على دراية بالعملية الشاقة التي ينطوي عليها نشر تطبيقاتهم ودفع التحديثات المستقبلية. لقد سهّل موفرو النظام الأساسي كخدمة (PaaS) نشر تطبيقات الويب دون الحاجة إلى متابعة عملية توفير وتكوين الخوادم الفردية ، مقابل زيادة طفيفة في التكاليف وانخفاض المرونة. ربما تكون PaaS قد سهلت الأمور ، ولكن في بعض الأحيان ما زلنا بحاجة أو نريد نشر التطبيقات على خوادمنا غير المُدارة. قد يبدو أتمتة عملية نشر تطبيقات الويب على الخادم الخاص بك أمرًا مربكًا في البداية ، ولكن في الواقع ، قد يكون التوصل إلى أداة بسيطة لأتمتة هذا أسهل مما تعتقد. يعتمد مدى سهولة تنفيذ هذه الأداة كثيرًا على مدى بساطة احتياجاتك ، ولكن بالتأكيد ليس من الصعب تحقيقها ، ويمكن أن تساعد في توفير الكثير من الوقت والجهد عن طريق القيام بالأجزاء المتكررة المملة من تطبيق الويب عمليات النشر.

توصل العديد من المطورين إلى طرقهم الخاصة لأتمتة عمليات نشر تطبيقات الويب الخاصة بهم. نظرًا لأن كيفية نشر تطبيقات الويب الخاصة بك تعتمد كثيرًا على مجموعة التكنولوجيا الدقيقة المستخدمة ، فإن حلول الأتمتة هذه تختلف بين بعضها البعض. على سبيل المثال ، تختلف الخطوات المتضمنة في النشر التلقائي لموقع PHP عن نشر تطبيق ويب Node.js. توجد حلول أخرى ، مثل Dokku ، وهي عامة جدًا وهذه الأشياء (تسمى buildpacks) تعمل بشكل جيد مع نطاق أوسع من مكدس التكنولوجيا.

تطبيقات الويب و webhooks

في هذا البرنامج التعليمي ، سنلقي نظرة على الأفكار الأساسية وراء أداة بسيطة يمكنك إنشاؤها لأتمتة عمليات نشر تطبيقات الويب الخاصة بك باستخدام GitHub webhooks و buildpacks و Procfiles. الكود المصدري لبرنامج النموذج الأولي الذي سنستكشفه في هذه المقالة متاح على GitHub.

الشروع في استخدام تطبيقات الويب

لأتمتة نشر تطبيق الويب الخاص بنا ، سنكتب برنامج Go بسيطًا. إذا لم تكن معتادًا على Go ، فلا تتردد في المتابعة ، لأن تركيبات الكود المستخدمة في هذه المقالة بسيطة إلى حد ما ويجب أن تكون سهلة الفهم. إذا كنت ترغب في ذلك ، يمكنك على الأرجح نقل البرنامج بأكمله إلى لغة من اختيارك بسهولة تامة.

قبل البدء ، تأكد من تثبيت توزيع Go على نظامك. لتثبيت Go ، يمكنك اتباع الخطوات الموضحة في الوثائق الرسمية.

بعد ذلك ، يمكنك تنزيل الكود المصدري لهذه الأداة عن طريق استنساخ مستودع GitHub. هذا من شأنه أن يسهل عليك المتابعة حيث تم تصنيف مقتطفات التعليمات البرمجية في هذه المقالة بأسماء الملفات المقابلة لها. إذا كنت ترغب في ذلك ، يمكنك تجربته على الفور.

تتمثل إحدى الميزات الرئيسية لاستخدام Go لهذا البرنامج في أنه يمكننا بنائه بطريقة يكون لدينا فيها الحد الأدنى من التبعيات الخارجية. في حالتنا ، لتشغيل هذا البرنامج على خادم ، نحتاج فقط إلى التأكد من تثبيت Git و Bash. نظرًا لأن برامج Go يتم تجميعها في ثنائيات مرتبطة بشكل ثابت ، يمكنك تجميع البرنامج على جهاز الكمبيوتر الخاص بك ، وتحميله على الخادم ، وتشغيله دون أي جهد تقريبًا. بالنسبة لمعظم اللغات الشائعة الأخرى اليوم ، قد يتطلب ذلك بيئة تشغيل ضخمة أو مترجمًا فوريًا مثبتًا على الخادم فقط لتشغيل أتمتة النشر. برامج Go ، عند القيام بها بشكل صحيح ، يمكن أن تكون سهلة للغاية في استخدام وحدة المعالجة المركزية وذاكرة الوصول العشوائي - وهو شيء تريده من برامج مثل هذه.

GitHub Webhooks

باستخدام GitHub Webhooks ، من الممكن تكوين مستودع GitHub الخاص بك لإصدار أحداث في كل مرة يتغير فيها شيء ما داخل المستودع أو يقوم بعض المستخدمين بتنفيذ إجراءات معينة على المستودع المستضاف. يتيح ذلك للمستخدمين الاشتراك في هذه الأحداث وإخطارهم من خلال استدعاءات URL بالأحداث المختلفة التي تحدث حول المستودع الخاص بك.

يعد إنشاء خطاف ويب بسيطًا جدًا:

  1. انتقل إلى صفحة الإعدادات الخاصة بالمستودع الخاص بك
  2. انقر على "Webhooks & Services" في قائمة التنقل اليسرى
  3. انقر فوق الزر "إضافة خطاف ويب"
  4. قم بتعيين عنوان URL ، واختيارياً سر (والذي سيسمح للمستلم بالتحقق من الحمولة)
  5. حدد اختيارات أخرى في النموذج ، حسب الضرورة
  6. قم بإرسال النموذج من خلال النقر على الزر الأخضر "إضافة خطاف ويب"

جيثب ويب هوك

يوفر GitHub وثائق مستفيضة عن Webhooks وكيف تعمل بالضبط ، وما هي المعلومات التي يتم تسليمها في الحمولة استجابة لأحداث مختلفة ، وما إلى ذلك. لغرض هذه المقالة ، نحن مهتمون بشكل خاص بحدث "push" الذي ينبعث في كل مرة يدفع إلى أي فرع مستودع.

Buildpacks

تعتبر Buildpacks قياسية إلى حد كبير هذه الأيام. تُستخدم حزم buildpack ، التي يستخدمها العديد من موفري PaaS ، بتحديد كيفية تكوين المكدس قبل نشر التطبيق. تعد كتابة حزم buildpack لتطبيق الويب الخاص بك أمرًا سهلاً حقًا ، ولكن في كثير من الأحيان يمكن أن يجد لك البحث السريع على الويب حزمة buildpack التي يمكنك استخدامها لتطبيق الويب الخاص بك دون أي تعديل.

إذا قمت بنشر تطبيق إلى PaaS مثل Heroku ، فقد تعرف بالفعل ما هي حزم buildpack وأين تجدها. لدى Heroku بعض الوثائق الشاملة حول هيكل حزم buildpack ، وقائمة ببعض حزم buildpack المشهورة.

سيستخدم برنامج الأتمتة الخاص بنا برمجيًا نصيًا لإعداد التطبيق قبل تشغيله. على سبيل المثال ، يقوم إنشاء Node.js بواسطة Heroku بتحليل ملف package.json وتنزيل إصدار مناسب من Node.js وتنزيل تبعيات NPM للتطبيق. تجدر الإشارة إلى أنه لإبقاء الأمور بسيطة ، لن يكون لدينا دعم مكثف لحزم buildpack في برنامج النموذج الأولي الخاص بنا. في الوقت الحالي ، سنفترض أن البرامج النصية buildpack مكتوبة ليتم تشغيلها مع Bash ، وأنها ستعمل على تثبيت Ubuntu جديد كما هو. إذا لزم الأمر ، يمكنك بسهولة توسيع هذا في المستقبل لتلبية المزيد من الاحتياجات الباطنية.

بروفيليس

ملفات Procfiles هي ملفات نصية بسيطة تتيح لك تحديد الأنواع المختلفة من العمليات التي لديك في تطبيقك. بالنسبة لمعظم التطبيقات البسيطة ، سيكون لديك بشكل مثالي عملية "ويب" واحدة والتي ستكون العملية التي تتعامل مع طلبات HTTP.

كتابة ملفات Procfiles سهلة. حدد نوع عملية واحدًا لكل سطر عن طريق كتابة اسمه ، متبوعًا بنقطتين ، متبوعًا بالأمر الذي سينتج العملية:

 <type>: <command>

على سبيل المثال ، إذا كنت تعمل باستخدام تطبيق ويب يستند إلى Node.js ، لبدء تشغيل خادم الويب ، يمكنك تنفيذ الأمر "node index.js". يمكنك ببساطة إنشاء ملف Procfile في الدليل الأساسي للرمز وتسميته "Procfile" بالشكل التالي:

 web: node index.js

سنطلب من التطبيقات تحديد أنواع العمليات في ملفات Procfiles حتى نتمكن من بدء تشغيلها تلقائيًا بعد سحب الكود.

معالجة الأحداث

في برنامجنا ، يجب أن نقوم بتضمين خادم HTTP يسمح لنا بتلقي طلبات POST الواردة من GitHub. سنحتاج إلى تخصيص بعض مسارات URL للتعامل مع هذه الطلبات من GitHub. ستبدو الوظيفة التي ستتعامل مع هذه الحمولات الواردة كما يلي:

 // hook.go type HookOptions struct { App *App Secret string } func NewHookHandler(o *HookOptions) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { evName := r.Header.Get("X-Github-Event") if evName != "push" { log.Printf("Ignoring '%s' event", evName) return } body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if o.Secret != "" { ok := false for _, sig := range strings.Fields(r.Header.Get("X-Hub-Signature")) { if !strings.HasPrefix(sig, "sha1=") { continue } sig = strings.TrimPrefix(sig, "sha1=") mac := hmac.New(sha1.New, []byte(o.Secret)) mac.Write(body) if sig == hex.EncodeToString(mac.Sum(nil)) { ok = true break } } if !ok { log.Printf("Ignoring '%s' event with incorrect signature", evName) return } } ev := github.PushEvent{} err = json.Unmarshal(body, &ev) if err != nil { log.Printf("Ignoring '%s' event with invalid payload", evName) http.Error(w, "Bad Request", http.StatusBadRequest) return } if ev.Repo.FullName == nil || *ev.Repo.FullName != o.App.Repo { log.Printf("Ignoring '%s' event with incorrect repository name", evName) http.Error(w, "Bad Request", http.StatusBadRequest) return } log.Printf("Handling '%s' event for %s", evName, o.App.Repo) err = o.App.Update() if err != nil { return } }) }

نبدأ بالتحقق من نوع الحدث الذي أدى إلى إنشاء هذه الحمولة. نظرًا لأننا مهتمون فقط بحدث "الدفع" ، يمكننا تجاهل جميع الأحداث الأخرى. حتى إذا قمت بتكوين webhook لإرسال أحداث "push" فقط ، فسيظل هناك نوع واحد آخر على الأقل من الأحداث التي يمكنك توقع تلقيها عند نقطة نهاية الخطاف: "ping". الغرض من هذا الحدث هو تحديد ما إذا تم تكوين خطاف الويب بنجاح على GitHub.

بعد ذلك ، نقرأ النص الكامل للطلب الوارد ، ونحسب HMAC-SHA1 الخاص به باستخدام نفس السر الذي سنستخدمه لتكوين خطاف الويب الخاص بنا ، وتحديد صلاحية الحمولة الواردة من خلال مقارنتها بالتوقيع المضمن في رأس طلب. في برنامجنا ، نتجاهل خطوة التحقق هذه إذا لم يتم تكوين السر. في ملاحظة جانبية ، قد لا يكون من الحكمة قراءة الجسم بالكامل دون وجود حد أعلى على الأقل لمقدار البيانات التي نريد التعامل معها هنا ، ولكن دعونا نجعل الأمور بسيطة للتركيز على الجوانب الحرجة من هذه الأداة.

ثم نستخدم بنية من مكتبة عميل GitHub لـ Go لإلغاء تنظيم الحمولة الواردة. نظرًا لأننا نعلم أنه حدث "دفع" ، يمكننا استخدام هيكل PushEvent. ثم نستخدم مكتبة تشفير json القياسية لإلغاء تنظيم الحمولة في مثيل للبنية. نجري فحصين للعقل ، وإذا كان كل شيء على ما يرام ، فإننا نستدعي الوظيفة التي تبدأ في تحديث تطبيقنا.

تحديث التطبيق

بمجرد أن نتلقى إشعارًا بالحدث عند نقطة نهاية خطاف الويب لدينا ، يمكننا البدء في تحديث تطبيقنا. في هذه المقالة ، سوف نلقي نظرة على تنفيذ بسيط إلى حد ما لهذه الآلية ، وسيكون هناك بالتأكيد مجال للتحسينات. ومع ذلك ، يجب أن يكون شيئًا ما سيقودنا إلى البدء في بعض عمليات النشر الآلي الأساسية.

مخطط تدفق تطبيق الويب هوك

تهيئة المستودع المحلي

ستبدأ هذه العملية بفحص بسيط لتحديد ما إذا كانت هذه هي المرة الأولى التي نحاول فيها نشر التطبيق. سنفعل ذلك عن طريق التحقق من وجود دليل المستودع المحلي. إذا لم يكن موجودًا ، فسنهيئ مستودعنا المحلي أولاً:

 // app.go func (a *App) initRepo() error { log.Print("Initializing repository") err := os.MkdirAll(a.repoDir, 0755) // Check err cmd := exec.Command("git", "--git-dir="+a.repoDir, "init") cmd.Stderr = os.Stderr err = cmd.Run() // Check err cmd = exec.Command("git", "--git-dir="+a.repoDir, "remote", "add", "origin", fmt.Sprintf("[email protected]:%s.git", a.Repo)) cmd.Stderr = os.Stderr err = cmd.Run() // Check err return nil }

يمكن استخدام هذه الطريقة في بنية التطبيق لتهيئة المستودع المحلي ، وآلياته بسيطة للغاية:

  1. قم بإنشاء دليل للمستودع المحلي إذا لم يكن موجودًا.
  2. استخدم الأمر "git init" لإنشاء مستودع فارغ.
  3. أضف عنوان URL للمستودع البعيد إلى مستودعنا المحلي ، وقم بتسميته "الأصل".

بمجرد أن يكون لدينا مستودع مُهيأ ، يجب أن يكون جلب التغييرات بسيطًا.

إحضار التغييرات

لجلب التغييرات من المستودع البعيد ، نحتاج فقط إلى استدعاء أمر واحد:

 // app.go func (a *App) fetchChanges() error { log.Print("Fetching changes") cmd := exec.Command("git", "--git-dir="+a.repoDir, "fetch", "-f", "origin", "master:master") cmd.Stderr = os.Stderr return cmd.Run() }

من خلال إجراء "إحضار git" لمستودعنا المحلي بهذه الطريقة ، يمكننا تجنب المشكلات المتعلقة بعدم قدرة Git على التقدم السريع في سيناريوهات معينة. لا يعني ذلك أن عمليات الجلب القسري هي شيء يجب أن تعتمد عليه ، ولكن إذا كنت بحاجة إلى دفع قوة إلى مستودعك البعيد ، فسيتعامل ذلك مع الأمر بأمان.

تجميع التطبيق

نظرًا لأننا نستخدم نصوصًا برمجية من buildpacks لتجميع تطبيقاتنا التي يتم نشرها ، فإن مهمتنا هنا سهلة نسبيًا:

 // app.go func (a *App) compileApp() error { log.Print("Compiling application") _, err := os.Stat(a.appDir) if !os.IsNotExist(err) { err = os.RemoveAll(a.appDir) // Check err } err = os.MkdirAll(a.appDir, 0755) // Check err cmd := exec.Command("git", "--git-dir="+a.repoDir, "--work-tree="+a.appDir, "checkout", "-f", "master") cmd.Dir = a.appDir cmd.Stderr = os.Stderr err = cmd.Run() // Check err buildpackDir, err := filepath.Abs("buildpack") // Check err cmd = exec.Command("bash", filepath.Join(buildpackDir, "bin", "detect"), a.appDir) cmd.Dir = buildpackDir cmd.Stderr = os.Stderr err = cmd.Run() // Check err cacheDir, err := filepath.Abs("cache") // Check err err = os.MkdirAll(cacheDir, 0755) // Check err cmd = exec.Command("bash", filepath.Join(buildpackDir, "bin", "compile"), a.appDir, cacheDir) cmd.Dir = a.appDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }

نبدأ بإزالة دليل التطبيق السابق (إن وجد). بعد ذلك ، نقوم بإنشاء واحد جديد وسحب محتويات الفرع الرئيسي إليه. ثم نستخدم البرنامج النصي "اكتشف" من buildpack المكوّن لتحديد ما إذا كان التطبيق أمرًا يمكننا التعامل معه. بعد ذلك ، نقوم بإنشاء دليل "cache" لعملية تجميع buildpack إذا لزم الأمر. نظرًا لاستمرار هذا الدليل عبر البنيات ، فقد يحدث أننا لا نضطر إلى إنشاء دليل جديد لأن واحدًا سيكون موجودًا بالفعل من بعض عمليات التجميع السابقة. في هذه المرحلة ، يمكننا استدعاء البرنامج النصي "compile" من buildpack وجعله يعد كل ما هو ضروري للتطبيق قبل الإطلاق. عندما يتم تشغيل buildpacks بشكل صحيح ، يمكنهم التعامل مع التخزين المؤقت وإعادة استخدام الموارد المخزنة مؤقتًا مسبقًا بمفردهم.

إعادة تشغيل التطبيق

أثناء تنفيذنا لعملية النشر الآلي هذه ، سنوقف العمليات القديمة قبل أن نبدأ عملية التجميع ، ثم نبدأ العمليات الجديدة بمجرد اكتمال مرحلة التجميع. على الرغم من أن هذا يجعل من السهل تنفيذ الأداة ، إلا أنها تترك بعض الطرق المدهشة لتحسين عملية النشر الآلي. لتحسين هذا النموذج الأولي ، ربما يمكنك البدء بضمان عدم حدوث أي توقف أثناء التحديثات. في الوقت الحالي ، سنواصل اتباع النهج الأبسط:

 // app.go func (a *App) stopProcs() error { log.Print(".. stopping processes") for _, n := range a.nodes { err := n.Stop() if err != nil { return err } } return nil } func (a *App) startProcs() error { log.Print("Starting processes") err := a.readProcfile() if err != nil { return err } for _, n := range a.nodes { err = n.Start() if err != nil { return err } } return nil }

في النموذج الأولي الخاص بنا ، نتوقف ونبدأ العمليات المختلفة عن طريق التكرار عبر مجموعة من العقد ، حيث تكون كل عقدة عملية مقابلة لإحدى مثيلات التطبيق (كما تم تكوينها قبل تشغيل هذه الأداة على الخادم). ضمن أداتنا ، نتتبع الحالة الحالية للعملية لكل عقدة. نحن نحتفظ أيضًا بملفات السجل الفردية لهم. قبل بدء جميع العقد ، يتم تعيين منفذ فريد لكل عقد يبدأ من رقم منفذ معين:

 // node.go func NewNode(app *App, name string, no int, port int) (*Node, error) { logFile, err := os.OpenFile(filepath.Join(app.logsDir, fmt.Sprintf("%s.%d.txt", name, no)), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { return nil, err } n := &Node{ App: app, Name: name, No: no, Port: port, stateCh: make(chan NextState), logFile: logFile, } go func() { for { next := <-n.stateCh if n.State == next.State { if next.doneCh != nil { close(next.doneCh) } continue } switch next.State { case StateUp: log.Printf("Starting process %s.%d", n.Name, n.No) cmd := exec.Command("bash", "-c", "for f in .profile.d/*; do source $f; done; "+n.Cmd) cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", n.App.appDir)) cmd.Env = append(cmd.Env, fmt.Sprintf("PORT=%d", n.Port)) cmd.Env = append(cmd.Env, n.App.Env...) cmd.Dir = n.App.appDir cmd.Stdout = n.logFile cmd.Stderr = n.logFile err := cmd.Start() if err != nil { log.Printf("Process %s.%d exited", n.Name, n.No) n.State = StateUp } else { n.Process = cmd.Process n.State = StateUp } if next.doneCh != nil { close(next.doneCh) } go func() { err := cmd.Wait() if err != nil { log.Printf("Process %s.%d exited", n.Name, n.No) n.stateCh <- NextState{ State: StateDown, } } }() case StateDown: log.Printf("Stopping process %s.%d", n.Name, n.No) if n.Process != nil { n.Process.Kill() n.Process = nil } n.State = StateDown if next.doneCh != nil { close(next.doneCh) } } } }() return n, nil } func (n *Node) Start() error { n.stateCh <- NextState{ State: StateUp, } return nil } func (n *Node) Stop() error { doneCh := make(chan int) n.stateCh <- NextState{ State: StateDown, doneCh: doneCh, } <-doneCh return nil }

في لمحة ، قد يبدو هذا أكثر تعقيدًا قليلاً مما فعلناه حتى الآن. لتسهيل فهم الأمور ، دعونا نقسم الكود أعلاه إلى أربعة أجزاء. الأولين ضمن وظيفة "NewNode". عند استدعائه ، فإنه يملأ مثيلًا لبنية "العقدة" ويولد روتين Go الذي يساعد في بدء وإيقاف العملية المقابلة لهذه العقدة. الطريقتان الأخريان هما طريقتان في بنية "Node": "Start" و "Stop". يتم بدء العملية أو إيقافها عن طريق تمرير "رسالة" عبر قناة معينة مفادها أن روتين Go لكل عقدة يراقبها. يمكنك إما تمرير رسالة لبدء العملية أو إرسال رسالة مختلفة لإيقافها. نظرًا لأن الخطوات الفعلية المتضمنة في بدء أو إيقاف العملية تحدث في روتين Go واحد ، فلا توجد فرصة للحصول على ظروف السباق.

يبدأ روتين Go حلقة لا نهائية حيث ينتظر "رسالة" عبر قناة "stateCh". إذا طلبت الرسالة التي تم تمريرها إلى هذه القناة من العقدة بدء العملية (داخل "case StateUp") ، فإنها تستخدم Bash لتنفيذ الأمر. أثناء القيام بذلك ، يقوم بتكوين الأمر لاستخدام متغيرات البيئة المعرفة من قبل المستخدم. يقوم أيضًا بإعادة توجيه الإخراج القياسي وتدفقات الخطأ إلى ملف سجل محدد مسبقًا.

من ناحية أخرى ، لإيقاف العملية (داخل "case StateDown") ، فإنها تقتلها ببساطة. هذا هو المكان الذي من المحتمل أن تكون فيه مبدعًا ، وبدلاً من قتل العملية على الفور أرسل SIGTERM وانتظر بضع ثوانٍ قبل قتلها فعليًا ، مما يمنح العملية فرصة للتوقف بأمان.

تعمل أساليب "البدء" و "الإيقاف" على تسهيل تمرير الرسالة المناسبة إلى القناة. على عكس طريقة "البدء" ، تنتظر طريقة "الإيقاف" في الواقع حتى يتم إنهاء العمليات قبل العودة. "ابدأ" ببساطة يمرر رسالة إلى القناة لبدء العملية والعودة.

الجمع بين كل شيء

أخيرًا ، كل ما نحتاج إلى القيام به هو توصيل كل شيء ضمن الوظيفة الرئيسية للبرنامج. هذا هو المكان الذي سنقوم فيه بتحميل ملف التكوين وتحليله ، وتحديث buildpack ، ومحاولة تحديث تطبيقنا مرة واحدة ، وبدء خادم الويب للاستماع إلى حمولات حدث "push" الواردة من GitHub:

 // main.go func main() { cfg, err := toml.LoadFile("config.tml") catch(err) url, ok := cfg.Get("buildpack.url").(string) if !ok { log.Fatal("buildpack.url not defined") } err = UpdateBuildpack(url) catch(err) // Read configuration options into variables repo (string), env ([]string) and procs (map[string]int) // ... app, err := NewApp(repo, env, procs) catch(err) err = app.Update() catch(err) secret, _ := cfg.Get("hook.secret").(string) http.Handle("/hook", NewHookHandler(&HookOptions{ App: app, Secret: secret, })) addr, ok := cfg.Get("core.addr").(string) if !ok { log.Fatal("core.addr not defined") } err = http.ListenAndServe(addr, nil) catch(err) }

نظرًا لأننا نطلب أن تكون حزم buildpack عبارة عن مستودعات Git بسيطة ، فإن "UpdateBuildpack" (تم تنفيذه في buildpack.go) يؤدي فقط "git clone" و "git pull" حسب الضرورة مع عنوان URL للمستودع لتحديث النسخة المحلية من buildpack.

تجربته

في حالة عدم استنساخ المستودع بعد ، يمكنك القيام بذلك الآن. إذا كان لديك توزيع Go مثبتًا ، فمن الممكن تجميع البرنامج على الفور.

 mkdir hopper cd hopper export GOPATH=`pwd` go get github.com/hjr265/toptal-hopper go install github.com/hjr265/toptal-hopper

سيؤدي هذا التسلسل من الأوامر إلى إنشاء دليل باسم النطاط ، وتعيينه كـ GOPATH ، وجلب الكود من GitHub جنبًا إلى جنب مع مكتبات Go الضرورية ، وتجميع البرنامج في ملف ثنائي يمكنك العثور عليه في دليل "$ GOPATH / bin". قبل أن نتمكن من استخدام هذا على الخادم ، نحتاج إلى إنشاء تطبيق ويب بسيط لاختبار ذلك باستخدام. للراحة ، قمت بإنشاء تطبيق ويب بسيط يشبه "Hello، world" يشبه Node.js وقمت بتحميله إلى مستودع GitHub آخر يمكنك تفرع منه وإعادة استخدامه لهذا الاختبار. بعد ذلك ، نحتاج إلى تحميل الملف الثنائي المترجم إلى خادم وإنشاء ملف تكوين في نفس الدليل:

 # config.tml [core] addr = ":26590" [buildpack] url = "https://github.com/heroku/heroku-buildpack-nodejs.git" [app] repo = "hjr265/hopper-hello.js" [app.env] GREETING = "Hello" [app.procs] web = 1 [hook] secret = ""

الخيار الأول في ملف التكوين الخاص بنا ، "core.addr" هو ما يتيح لنا تكوين منفذ HTTP لخادم الويب الداخلي لبرنامجنا. في المثال أعلاه ، قمنا بتعيينه على ": 26590" ، مما سيجعل البرنامج يستمع إلى حمولات حدث "push" على "http: // {host}: 26590 / hook". عند إعداد خطاف الويب لـ GitHub ، ما عليك سوى استبدال "{host}" باسم المجال أو عنوان IP الذي يشير إلى خادمك. تأكد من فتح المنفذ في حال كنت تستخدم نوعًا من جدار الحماية.

بعد ذلك ، نختار buildpack من خلال تعيين عنوان URL الخاص بـ Git. نحن هنا نستخدم حزمة Node.js من Heroku.

ضمن "التطبيق" ، قمنا بتعيين "repo" على الاسم الكامل لمستودع GitHub الذي يستضيف رمز التطبيق. نظرًا لأنني أستضيف نموذج التطبيق على "https://github.com/hjr265/hopper-hello.js" ، فإن الاسم الكامل للمستودع هو "hjr265 / hopper-hello.js".

ثم نضع بعض متغيرات البيئة للتطبيق ، وعدد كل نوع من العمليات التي نحتاجها. وأخيرًا ، نختار سرًا ، حتى نتمكن من التحقق من حمولات حدث "الدفع" الوارد.

يمكننا الآن بدء برنامج التشغيل الآلي الخاص بنا على الخادم. إذا تم تكوين كل شيء بشكل صحيح (بما في ذلك نشر مفاتيح SSH ، بحيث يمكن الوصول إلى المستودع من الخادم) ، يجب على البرنامج جلب الرمز ، وإعداد البيئة باستخدام buildpack ، وتشغيل التطبيق. كل ما نحتاجه الآن هو إعداد خطاف ويب في مستودع GitHub لإرسال أحداث الدفع وتوجيهها إلى "http: // {host}: 26590 / hook". تأكد من استبدال "{host}" باسم المجال أو عنوان IP الذي يشير إلى خادمك.

لاختباره أخيرًا ، قم بإجراء بعض التغييرات على التطبيق النموذجي وادفعها إلى GitHub. ستلاحظ أن أداة الأتمتة ستبدأ العمل فورًا وتقوم بتحديث المستودع على الخادم ، وتجميع التطبيق ، وإعادة تشغيله.

خاتمة

من معظم تجاربنا ، يمكننا أن نقول أن هذا شيء مفيد للغاية. قد لا يكون تطبيق النموذج الأولي الذي أعددناه في هذه المقالة شيئًا تريد استخدامه في نظام الإنتاج كما هو. هناك مساحة كبيرة للتحسين. يجب أن تتمتع أداة مثل هذه بمعالجة أفضل للأخطاء ، وأن تدعم عمليات إيقاف التشغيل / إعادة التشغيل الرائعة ، وقد ترغب في استخدام شيء مثل Docker لاحتواء العمليات بدلاً من تشغيلها مباشرةً. قد يكون من الحكمة معرفة ما تحتاجه بالضبط لحالتك الخاصة ، والتوصل إلى برنامج أتمتة لذلك. أو ربما تستخدم بعض الحلول الأخرى الأكثر استقرارًا والتي تم اختبارها عبر الزمن والمتوفرة في جميع أنحاء الإنترنت. ولكن في حالة رغبتك في طرح شيء مخصص للغاية ، آمل أن تساعدك هذه المقالة في القيام بذلك وإظهار مقدار الوقت والجهد الذي يمكنك توفيره على المدى الطويل من خلال أتمتة عملية نشر تطبيق الويب.

ذات صلة: شرح تدفق Git المحسن