Menyebarkan Aplikasi Web Secara Otomatis Menggunakan GitHub Webhooks

Diterbitkan: 2022-03-11

Siapa pun yang mengembangkan aplikasi web dan mencoba menjalankannya di server mereka sendiri yang tidak terkelola menyadari proses membosankan yang terkait dengan penerapan aplikasi mereka dan mendorong pembaruan di masa mendatang. Penyedia platform sebagai layanan (PaaS) telah mempermudah penerapan aplikasi web tanpa harus melalui proses penyediaan dan konfigurasi server individual, dengan imbalan sedikit peningkatan biaya dan penurunan fleksibilitas. PaaS mungkin telah membuat segalanya lebih mudah, tetapi terkadang kita masih perlu atau ingin menerapkan aplikasi di server kita sendiri yang tidak terkelola. Mengotomatiskan proses penerapan aplikasi web ke server Anda mungkin terdengar berlebihan pada awalnya, tetapi pada kenyataannya membuat alat sederhana untuk mengotomatisasi ini mungkin lebih mudah daripada yang Anda pikirkan. Seberapa mudah untuk mengimplementasikan alat ini sangat bergantung pada seberapa sederhana kebutuhan Anda, tetapi tentu saja tidak sulit untuk dicapai, dan mungkin dapat membantu menghemat banyak waktu dan tenaga dengan melakukan bagian-bagian berulang yang membosankan dari aplikasi web penyebaran.

Banyak pengembang telah menemukan cara mereka sendiri untuk mengotomatisasi proses penyebaran aplikasi web mereka. Karena cara Anda menerapkan aplikasi web Anda sangat bergantung pada tumpukan teknologi tepat yang digunakan, solusi otomatisasi ini berbeda satu sama lain. Misalnya, langkah-langkah yang terlibat dalam penerapan situs web PHP secara otomatis berbeda dengan penerapan aplikasi web Node.js. Ada solusi lain, seperti Dokku, yang cukup umum dan hal-hal ini (disebut buildpacks) bekerja dengan baik dengan jangkauan tumpukan teknologi yang lebih luas.

aplikasi web dan webhook

Dalam tutorial ini, kita akan melihat ide dasar di balik alat sederhana yang dapat Anda buat untuk mengotomatiskan penerapan aplikasi web Anda menggunakan webhook, buildpack, dan Procfiles GitHub. Kode sumber program prototipe yang akan kita jelajahi dalam artikel ini tersedia di GitHub.

Memulai dengan Aplikasi Web

Untuk mengotomatisasi penerapan aplikasi web kami, kami akan menulis program Go sederhana. Jika Anda tidak terbiasa dengan Go, jangan ragu untuk mengikutinya, karena konstruksi kode yang digunakan di seluruh artikel ini cukup sederhana dan harus mudah dipahami. Jika Anda menginginkannya, Anda mungkin dapat mem-porting seluruh program ke dalam bahasa pilihan Anda dengan cukup mudah.

Sebelum memulai, pastikan Anda telah menginstal distribusi Go di sistem Anda. Untuk menginstal Go, Anda dapat mengikuti langkah-langkah yang dijelaskan dalam dokumentasi resmi.

Selanjutnya, Anda dapat mengunduh kode sumber alat ini dengan mengkloning repositori GitHub. Ini akan memudahkan Anda untuk mengikuti karena cuplikan kode dalam artikel ini diberi label dengan nama file yang sesuai. Jika Anda mau, Anda bisa mencobanya langsung.

Salah satu keuntungan utama menggunakan Go untuk program ini adalah kita dapat membangunnya dengan cara di mana kita memiliki ketergantungan eksternal yang minimal. Dalam kasus kami, untuk menjalankan program ini di server kami hanya perlu memastikan bahwa kami telah menginstal Git dan Bash. Karena program Go dikompilasi ke dalam binari yang terhubung secara statis, Anda dapat mengompilasi program di komputer Anda, mengunggahnya ke server, dan menjalankannya dengan hampir tanpa usaha. Untuk sebagian besar bahasa populer lainnya saat ini, ini akan memerlukan beberapa lingkungan runtime raksasa atau juru bahasa yang diinstal di server hanya untuk menjalankan automator penerapan Anda. Buka program, bila dilakukan dengan benar, juga bisa sangat mudah menggunakan CPU dan RAM - yang merupakan sesuatu yang Anda inginkan dari program seperti ini.

Webhook GitHub

Dengan GitHub Webhooks, dimungkinkan untuk mengonfigurasi repositori GitHub Anda untuk memancarkan peristiwa setiap kali sesuatu berubah dalam repositori atau beberapa pengguna melakukan tindakan tertentu pada repositori yang dihosting. Ini memungkinkan pengguna untuk berlangganan acara ini dan diberi tahu melalui pemanggilan URL dari berbagai acara yang terjadi di sekitar repositori Anda.

Membuat webhook sangat sederhana:

  1. Arahkan ke halaman pengaturan repositori Anda
  2. Klik "Webhook & Layanan" di menu navigasi kiri
  3. Klik tombol “Tambahkan webhook”
  4. Tetapkan URL, dan secara opsional rahasia (yang memungkinkan penerima memverifikasi muatan)
  5. Buat pilihan lain pada formulir, jika perlu
  6. Kirim formulir dengan mengklik tombol hijau “Tambah webhook”

Webhook Github

GitHub menyediakan dokumentasi ekstensif tentang Webhooks dan bagaimana tepatnya mereka bekerja, informasi apa yang disampaikan dalam muatan sebagai respons terhadap berbagai peristiwa, dll. Untuk tujuan artikel ini, kami secara khusus tertarik pada peristiwa "push" yang dipancarkan setiap kali seseorang mendorong ke cabang repositori mana pun.

Buildpack

Buildpack cukup standar akhir-akhir ini. Digunakan oleh banyak penyedia PaaS, buildpack memungkinkan Anda untuk menentukan bagaimana stack akan dikonfigurasi sebelum aplikasi di-deploy. Menulis buildpack untuk aplikasi web Anda sangat mudah, tetapi lebih sering daripada tidak pencarian cepat di web dapat menemukan buildpack yang dapat Anda gunakan untuk aplikasi web Anda tanpa modifikasi apa pun.

Jika Anda telah menerapkan aplikasi ke PaaS seperti Heroku, Anda mungkin sudah tahu apa itu buildpack dan di mana menemukannya. Heroku memiliki beberapa dokumentasi komprehensif tentang struktur buildpack, dan daftar beberapa buildpack populer yang dibuat dengan baik.

Program otomatisasi kami akan menggunakan skrip kompilasi untuk menyiapkan aplikasi sebelum meluncurkannya. Misalnya, build Node.js oleh Heroku mem-parsing file package.json, mengunduh versi Node.js yang sesuai, dan mengunduh dependensi NPM untuk aplikasi. Perlu dicatat bahwa untuk menjaga semuanya tetap sederhana, kami tidak akan memiliki dukungan ekstensif untuk buildpack dalam program prototipe kami. Untuk saat ini, kami akan berasumsi bahwa skrip buildpack ditulis untuk dijalankan dengan Bash, dan skrip tersebut akan berjalan pada instalasi Ubuntu yang baru seperti apa adanya. Jika perlu, Anda dapat dengan mudah memperluas ini di masa mendatang untuk memenuhi kebutuhan yang lebih esoteris.

Profil

Procfiles adalah file teks sederhana yang memungkinkan Anda untuk menentukan berbagai jenis proses yang Anda miliki dalam aplikasi Anda. Untuk sebagian besar aplikasi sederhana, idealnya Anda memiliki satu proses "web" yang akan menjadi proses yang menangani permintaan HTTP.

Menulis Profil itu mudah. Tentukan satu jenis proses per baris dengan mengetikkan namanya, diikuti dengan titik dua, diikuti dengan perintah yang akan memunculkan proses:

 <type>: <command>

Misalnya, jika Anda bekerja dengan aplikasi web berbasis Node.js, untuk memulai server web Anda akan menjalankan perintah “node index.js”. Anda cukup membuat Procfile di direktori dasar kode dan beri nama "Procfile" dengan yang berikut:

 web: node index.js

Kami akan membutuhkan aplikasi untuk menentukan jenis proses di Procfiles sehingga kami dapat memulainya secara otomatis setelah memasukkan kode.

Menangani Acara

Dalam program kita, kita harus menyertakan server HTTP yang memungkinkan kita menerima permintaan POST masuk dari GitHub. Kami perlu mendedikasikan beberapa jalur URL untuk menangani permintaan ini dari GitHub. Fungsi yang akan menangani muatan yang masuk ini akan terlihat seperti ini:

 // 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 } }) }

Kami mulai dengan memverifikasi jenis acara yang telah menghasilkan muatan ini. Karena kita hanya tertarik pada event “push”, kita bisa mengabaikan semua event lainnya. Bahkan jika Anda mengonfigurasi webhook untuk hanya memancarkan peristiwa "push", masih akan ada setidaknya satu jenis peristiwa lain yang dapat Anda harapkan untuk diterima di titik akhir kait Anda: "ping". Tujuan acara ini adalah untuk menentukan apakah webhook telah berhasil dikonfigurasi di GitHub.

Selanjutnya, kita membaca seluruh isi permintaan yang masuk, menghitung HMAC-SHA1-nya menggunakan rahasia yang sama yang akan kita gunakan untuk mengonfigurasi webhook kita, dan menentukan validitas muatan yang masuk dengan membandingkannya dengan tanda tangan yang disertakan dalam header dari meminta. Dalam program kami, kami mengabaikan langkah validasi ini jika rahasia tidak dikonfigurasi. Di samping catatan, mungkin bukan ide yang bijaksana untuk membaca seluruh tubuh tanpa setidaknya memiliki semacam batas atas berapa banyak data yang ingin kita tangani di sini, tetapi mari kita tetap sederhana untuk fokus pada aspek-aspek kritis. dari alat ini.

Kemudian kami menggunakan struct dari pustaka klien GitHub untuk Go untuk membongkar muatan yang masuk. Karena kita tahu ini adalah event “push”, kita bisa menggunakan struct PushEvent. Kami kemudian menggunakan perpustakaan pengkodean json standar untuk menghapus muatan menjadi turunan dari struct. Kami melakukan beberapa pemeriksaan kewarasan, dan jika semuanya baik-baik saja, kami memanggil fungsi yang mulai memperbarui aplikasi kami.

Memperbarui Aplikasi

Setelah kami menerima pemberitahuan acara di titik akhir webhook kami, kami dapat mulai memperbarui aplikasi kami. Pada artikel ini, kita akan melihat implementasi yang cukup sederhana dari mekanisme ini, dan tentunya akan ada ruang untuk perbaikan. Namun, itu harus menjadi sesuatu yang akan membantu kita memulai dengan beberapa proses penyebaran otomatis dasar.

diagram alur aplikasi webhook

Menginisialisasi Repositori Lokal

Proses ini akan dimulai dengan pemeriksaan sederhana untuk menentukan apakah ini pertama kalinya kami mencoba menerapkan aplikasi. Kami akan melakukannya dengan memeriksa apakah direktori repositori lokal ada. Jika tidak ada, kami akan menginisialisasi repositori lokal kami terlebih dahulu:

 // 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 }

Metode pada App struct ini dapat digunakan untuk menginisialisasi repositori lokal, dan mekanismenya sangat sederhana:

  1. Buat direktori untuk repositori lokal jika tidak ada.
  2. Gunakan perintah "git init" untuk membuat repositori kosong.
  3. Tambahkan URL untuk repositori jarak jauh ke repositori lokal kami, dan beri nama "asal".

Setelah kita memiliki repositori yang diinisialisasi, mengambil perubahan seharusnya sederhana.

Mengambil Perubahan

Untuk mengambil perubahan dari repositori jarak jauh, kita hanya perlu menjalankan satu perintah:

 // 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() }

Dengan melakukan "git fetch" untuk repositori lokal kami dengan cara ini, kami dapat menghindari masalah dengan Git yang tidak dapat melakukan fast-forward dalam skenario tertentu. Bukannya pengambilan paksa adalah sesuatu yang harus Anda andalkan, tetapi jika Anda perlu melakukan dorongan paksa ke repositori jarak jauh Anda, ini akan menanganinya dengan baik.

Aplikasi kompilasi

Karena kami menggunakan skrip dari buildpacks untuk mengkompilasi aplikasi kami yang sedang digunakan, tugas kami di sini relatif mudah:

 // 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() }

Kami mulai dengan menghapus direktori aplikasi kami sebelumnya (jika ada). Selanjutnya, kami membuat yang baru dan memeriksa konten cabang master ke sana. Kami kemudian menggunakan skrip "deteksi" dari buildpack yang dikonfigurasi untuk menentukan apakah aplikasi adalah sesuatu yang dapat kami tangani. Kemudian, kita membuat direktori “cache” untuk proses kompilasi buildpack jika diperlukan. Karena direktori ini tetap ada di seluruh build, mungkin saja kita tidak perlu membuat direktori baru karena direktori tersebut sudah ada dari beberapa proses kompilasi sebelumnya. Pada titik ini, kita dapat memanggil skrip "kompilasi" dari buildpack dan membuatnya menyiapkan semua yang diperlukan untuk aplikasi sebelum diluncurkan. Ketika buildpack dijalankan dengan benar, mereka dapat menangani caching dan penggunaan kembali sumber daya yang sebelumnya di-cache sendiri.

Memulai Ulang Aplikasi

Dalam implementasi proses penerapan otomatis ini, kami akan menghentikan proses lama sebelum memulai proses kompilasi, dan kemudian memulai proses baru setelah fase kompilasi selesai. Meskipun ini memudahkan untuk mengimplementasikan alat, ini meninggalkan beberapa cara yang berpotensi luar biasa untuk meningkatkan proses penerapan otomatis. Untuk meningkatkan prototipe ini, Anda mungkin dapat memulai dengan memastikan tidak ada waktu henti selama pembaruan. Untuk saat ini, kami akan melanjutkan dengan pendekatan yang lebih sederhana:

 // 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 }

Dalam prototipe kami, kami menghentikan dan memulai berbagai proses dengan mengulangi array node, di mana setiap node adalah proses yang sesuai dengan salah satu contoh aplikasi (seperti yang dikonfigurasi sebelum meluncurkan alat ini di server). Dalam alat kami, kami melacak status proses saat ini untuk setiap node. Kami juga memelihara file log individual untuk mereka. Sebelum semua node dimulai, masing-masing diberi port unik mulai dari nomor port yang diberikan:

 // 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 }

Sepintas, ini mungkin tampak sedikit lebih rumit daripada apa yang telah kita lakukan sejauh ini. Untuk mempermudah pemahaman, mari kita bagi kode di atas menjadi empat bagian. Dua yang pertama berada dalam fungsi "NewNode". Saat dipanggil, itu mengisi instance dari struct "Node" dan memunculkan rutinitas Go yang membantu memulai dan menghentikan proses yang sesuai dengan Node ini. Dua lainnya adalah dua metode pada struct "Node": "Start" dan "Stop". Sebuah proses dimulai atau dihentikan dengan melewatkan "pesan" melalui saluran tertentu yang diawasi oleh rutin Go per-node ini. Anda dapat mengirimkan pesan untuk memulai proses atau pesan lain untuk menghentikannya. Karena langkah-langkah aktual yang terlibat dalam memulai atau menghentikan suatu proses terjadi dalam satu rutinitas Go, tidak ada peluang untuk mendapatkan kondisi balapan.

Rutinitas Go memulai loop tak terbatas di mana ia menunggu "pesan" melalui saluran "stateCh". Jika pesan yang diteruskan ke saluran ini meminta node untuk memulai proses (di dalam "case StateUp"), ia menggunakan Bash untuk menjalankan perintah. Saat melakukan itu, ia mengonfigurasi perintah untuk menggunakan variabel lingkungan yang ditentukan pengguna. Ini juga mengarahkan output standar dan aliran kesalahan ke file log yang telah ditentukan sebelumnya.

Di sisi lain, untuk menghentikan proses (di dalam "case StateDown"), itu hanya membunuhnya. Di sinilah Anda mungkin bisa menjadi kreatif, dan alih-alih menghentikan proses, segera kirimkan SIGTERM dan tunggu beberapa detik sebelum benar-benar mematikannya, memberikan proses kesempatan untuk berhenti dengan anggun.

Metode "Mulai" dan "Berhenti" memudahkan untuk menyampaikan pesan yang sesuai ke saluran. Berbeda dengan metode "Mulai", metode "Berhenti" sebenarnya menunggu proses dimatikan sebelum kembali. "Mulai" hanya meneruskan pesan ke saluran untuk memulai proses dan kembali.

Menggabungkan Semuanya

Akhirnya, yang perlu kita lakukan hanyalah menghubungkan semuanya ke dalam fungsi utama program. Di sinilah kita akan memuat dan mengurai file konfigurasi, memperbarui buildpack, mencoba memperbarui aplikasi kita sekali, dan memulai server web untuk mendengarkan payload acara "push" yang masuk dari 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) }

Karena kami memerlukan buildpack sebagai repositori Git sederhana, “UpdateBuildpack” (diimplementasikan dalam buildpack.go) hanya menjalankan “git clone” dan “git pull” seperlunya dengan URL repositori untuk memperbarui salinan lokal buildpack.

Mencobanya

Jika Anda belum mengkloning repositori, Anda dapat melakukannya sekarang. Jika Anda telah menginstal distribusi Go, program tersebut akan dapat segera dikompilasi.

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

Urutan perintah ini akan membuat direktori bernama hopper, menetapkannya sebagai GOPATH, mengambil kode dari GitHub bersama dengan pustaka Go yang diperlukan, dan mengompilasi program ke dalam biner yang dapat Anda temukan di direktori “$GOPATH/bin”. Sebelum kita dapat menggunakan ini di server, kita perlu membuat aplikasi web sederhana untuk mengujinya. Untuk kenyamanan, saya telah membuat aplikasi web Node.js seperti “Halo, dunia” sederhana dan mengunggahnya ke repositori GitHub lain yang dapat Anda fork dan gunakan kembali untuk pengujian ini. Selanjutnya, kita perlu mengunggah biner yang dikompilasi ke server dan membuat file konfigurasi di direktori yang sama:

 # 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 = ""

Opsi pertama dalam file konfigurasi kami, "core.addr" adalah apa yang memungkinkan kami mengkonfigurasi port HTTP dari server web internal program kami. Pada contoh di atas, kita telah menyetelnya ke “:26590, yang akan membuat program mendengarkan payload event “push” di “http://{host}:26590/hook”. Saat menyiapkan webhook GitHub, cukup ganti “{host}” dengan nama domain atau alamat IP yang mengarah ke server Anda. Pastikan port terbuka jika Anda menggunakan semacam firewall.

Selanjutnya, kami memilih buildpack dengan mengatur URL Git-nya. Di sini kita menggunakan buildpack Node.js Heroku.

Di bawah "aplikasi", kami menetapkan "repo" ke nama lengkap repositori GitHub Anda yang menghosting kode aplikasi. Karena saya menghosting contoh aplikasi di "https://github.com/hjr265/hopper-hello.js", nama lengkap repositori adalah "hjr265/hopper-hello.js".

Kemudian kami menetapkan beberapa variabel lingkungan untuk aplikasi, dan jumlah setiap jenis proses yang kami butuhkan. Dan akhirnya, kami memilih sebuah rahasia, sehingga kami dapat memverifikasi muatan acara "push" yang masuk.

Kami sekarang dapat memulai program otomatisasi kami di server. Jika semuanya dikonfigurasi dengan benar (termasuk menyebarkan kunci SSH, sehingga repositori dapat diakses dari server), program harus mengambil kode, menyiapkan lingkungan menggunakan buildpack, dan meluncurkan aplikasi. Sekarang yang perlu kita lakukan adalah menyiapkan webhook di repositori GitHub untuk memancarkan peristiwa push dan mengarahkannya ke “http://{host}:26590/hook”. Pastikan Anda mengganti “{host}” dengan nama domain atau alamat IP yang mengarah ke server Anda.

Untuk akhirnya mengujinya, buat beberapa perubahan pada aplikasi contoh dan dorong ke GitHub. Anda akan melihat bahwa alat otomatisasi akan segera beraksi dan memperbarui repositori di server, mengkompilasi aplikasi, dan memulai ulang.

Kesimpulan

Dari sebagian besar pengalaman kami, kami dapat mengatakan bahwa ini adalah sesuatu yang sangat berguna. Aplikasi prototipe yang telah kami siapkan dalam artikel ini mungkin bukan sesuatu yang ingin Anda gunakan pada sistem produksi apa adanya. Ada banyak ruang untuk perbaikan. Alat seperti ini seharusnya memiliki penanganan kesalahan yang lebih baik, mendukung shutdown/restart yang anggun, dan Anda mungkin ingin menggunakan sesuatu seperti Docker untuk memuat proses alih-alih menjalankannya secara langsung. Mungkin lebih bijaksana untuk mencari tahu apa sebenarnya yang Anda butuhkan untuk kasus spesifik Anda, dan membuat program otomatisasi untuk itu. Atau mungkin gunakan solusi lain yang jauh lebih stabil dan teruji waktu yang tersedia di seluruh Internet. Tetapi jika Anda ingin meluncurkan sesuatu yang sangat disesuaikan, saya harap artikel ini akan membantu Anda melakukannya dan menunjukkan berapa banyak waktu dan upaya yang dapat Anda hemat dalam jangka panjang dengan mengotomatiskan proses penerapan aplikasi web Anda.

Terkait: Penjelasan Aliran Git yang Ditingkatkan