مساء الخير يا أصدقاء. تحسبا لبداية تدفق جديد بالمعدل
يوفر استخدام لغة Pulumi ولغات البرمجة ذات الأغراض العامة لكود البنية التحتية (البنية التحتية كرمز) العديد من المزايا: توفر المهارات والمعرفة، والتخلص من القالب المعياري في الكود من خلال التجريد، والأدوات المألوفة لفريقك، مثل IDEs وinters. كل أدوات هندسة البرمجيات هذه لا تجعلنا أكثر إنتاجية فحسب، بل تعمل أيضًا على تحسين جودة التعليمات البرمجية لدينا. لذلك، فمن الطبيعي أن يتيح لنا استخدام لغات البرمجة ذات الأغراض العامة تقديم ممارسة مهمة أخرى لتطوير البرمجيات - اختبار.
في هذه المقالة، سنلقي نظرة على كيفية مساعدة Pulumi لنا في اختبار بنيتنا التحتية كرمز.
لماذا اختبار البنية التحتية؟
قبل الخوض في التفاصيل، يجدر طرح السؤال: "لماذا يتم اختبار البنية التحتية أصلاً؟" هناك أسباب كثيرة لذلك وهنا بعض منها:
- اختبار الوحدة للوظائف الفردية أو أجزاء من منطق البرنامج الخاص بك
- التحقق من الحالة المرغوبة للبنية التحتية مقابل قيود معينة.
- اكتشاف الأخطاء الشائعة، مثل عدم تشفير مجموعة التخزين أو الوصول المفتوح غير المحمي من الإنترنت إلى الأجهزة الافتراضية.
- التحقق من تنفيذ توفير البنية التحتية.
- إجراء اختبار وقت التشغيل لمنطق التطبيق الذي يعمل داخل البنية الأساسية "المبرمجة" الخاصة بك للتحقق من الوظائف بعد التزويد.
- كما نرى، هناك مجموعة واسعة من خيارات اختبار البنية التحتية. لدى بولومي آليات للاختبار في كل نقطة على هذا الطيف. دعونا نبدأ ونرى كيف يعمل.
وحدة التجارب
تتم كتابة برامج Pulumi بلغات برمجة للأغراض العامة مثل JavaScript أو Python أو TypeScript أو Go. ولذلك، فإن القوة الكاملة لهذه اللغات، بما في ذلك أدواتها ومكتباتها، بما في ذلك أطر الاختبار، متاحة لهم. Pulumi عبارة عن سحابة متعددة، مما يعني أنه يمكن استخدامها للاختبار من أي مزود سحابي.
(في هذه المقالة، على الرغم من كونها متعددة اللغات ومتعددة السحابة، فإننا نستخدم JavaScript وMocha ونركز على AWS. يمكنك استخدام Python unittest
أو إطار اختبار Go أو أي إطار اختبار آخر تفضله. وبالطبع، يعمل Pulumi بشكل رائع مع Azure وGoogle Cloud وKubernetes.)
كما رأينا، هناك عدة أسباب وراء رغبتك في اختبار كود البنية التحتية لديك. واحد منهم هو اختبار الوحدة التقليدية. لأن الكود الخاص بك قد يحتوي على وظائف - على سبيل المثال، لحساب CIDR، وحساب الأسماء والعلامات ديناميكيًا، وما إلى ذلك. - ربما تريد اختبارها. وهذا يشبه كتابة اختبارات الوحدة العادية للتطبيقات بلغة البرمجة المفضلة لديك.
ولكي تصبح أكثر تعقيدًا، يمكنك التحقق من كيفية تخصيص برنامجك للموارد. للتوضيح، لنتخيل أننا بحاجة إلى إنشاء خادم EC2 بسيط ونريد التأكد مما يلي:
- المثيلات لها علامة
Name
. - يجب ألا تستخدم المثيلات برنامجًا نصيًا مضمنًا
userData
- يجب علينا استخدام AMI (صورة). - يجب ألا يكون هناك SSH مكشوفًا للإنترنت.
ويستند هذا المثال على
مؤشر.js:
"use strict";
let aws = require("@pulumi/aws");
let group = new aws.ec2.SecurityGroup("web-secgrp", {
ingress: [
{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
],
});
let userData =
`#!/bin/bash
echo "Hello, World!" > index.html
nohup python -m SimpleHTTPServer 80 &`;
let server = new aws.ec2.Instance("web-server-www", {
instanceType: "t2.micro",
securityGroups: [ group.name ], // reference the group object above
ami: "ami-c55673a0" // AMI for us-east-2 (Ohio),
userData: userData // start a simple web server
});
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
هذا هو برنامج Pulumi الأساسي: فهو ببساطة يخصص مجموعة أمان EC2 ومثيلًا. ومع ذلك، تجدر الإشارة إلى أننا هنا نكسر القواعد الثلاثة المذكورة أعلاه. دعونا نكتب الاختبارات!
اختبارات الكتابة
سيبدو الهيكل العام لاختباراتنا مثل اختبارات Mocha العادية:
ec2tests.js
test.js:
let assert = require("assert");
let mocha = require("mocha");
let pulumi = require("@pulumi/pulumi");
let infra = require("./index");
describe("Infrastructure", function() {
let server = infra.server;
describe("#server", function() {
// TODO(check 1): Должен быть тэг Name.
// TODO(check 2): Не должно быть inline-скрипта userData.
});
let group = infra.group;
describe("#group", function() {
// TODO(check 3): Не должно быть SSH, открытого в Интернет.
});
});
لنكتب الآن اختبارنا الأول: تأكد من أن المثيلات تحتوي على العلامة Name
. للتحقق من ذلك، نحصل ببساطة على كائن مثيل EC2 ونتحقق من الخاصية المقابلة tags
:
// check 1: Должен быть тэг Name.
it("must have a name tag", function(done) {
pulumi.all([server.urn, server.tags]).apply(([urn, tags]) => {
if (!tags || !tags["Name"]) {
done(new Error(`Missing a name tag on server ${urn}`));
} else {
done();
}
});
});
يبدو وكأنه اختبار عادي، ولكن مع بعض الميزات الجديرة بالملاحظة:
- نظرًا لأننا نقوم بالاستعلام عن حالة المورد قبل النشر، فإن اختباراتنا يتم تشغيلها دائمًا في وضع "التخطيط" (أو "المعاينة"). وبالتالي، هناك العديد من الخصائص التي لن يتم استرجاع قيمها أو تحديدها. يتضمن ذلك جميع خصائص المخرجات التي يحسبها موفر السحابة الخاص بك. وهذا أمر طبيعي بالنسبة لاختباراتنا - فنحن نتحقق فقط من البيانات المدخلة. سنعود إلى هذه المشكلة لاحقًا، عندما يتعلق الأمر باختبارات التكامل.
- نظرًا لأن جميع خصائص موارد Pulumi هي مخرجات، ويتم تقييم العديد منها بشكل غير متزامن، فنحن بحاجة إلى استخدام الأسلوب application للوصول إلى القيم. هذا مشابه جدًا للوعود والوظيفة
then
. - نظرًا لأننا نستخدم العديد من الخصائص لإظهار مورد URN في رسالة الخطأ، فنحن بحاجة إلى استخدام الوظيفة
pulumi.all
للجمع بينهما. - أخيرًا، بما أن هذه القيم يتم حسابها بشكل غير متزامن، فنحن بحاجة إلى استخدام ميزة رد الاتصال غير المتزامن المضمنة في Mocha
done
أو إرجاع الوعد.
بمجرد الانتهاء من إعداد كل شيء، سيكون لدينا إمكانية الوصول إلى المدخلات كقيم JavaScript بسيطة. ملكية tags
هي خريطة (مصفوفة ترابطية)، لذلك سنتأكد فقط من أنها (1) ليست خاطئة، و(2) يوجد مفتاح لـ Name
. الأمر بسيط جدًا والآن يمكننا اختبار أي شيء!
الآن دعونا نكتب الشيك الثاني. إنها أبسط:
// check 2: Не должно быть inline-скрипта userData.
it("must not use userData (use an AMI instead)", function(done) {
pulumi.all([server.urn, server.userData]).apply(([urn, userData]) => {
if (userData) {
done(new Error(`Illegal use of userData on server ${urn}`));
} else {
done();
}
});
});
وأخيرا، دعونا نكتب الاختبار الثالث. سيكون هذا أكثر تعقيدًا بعض الشيء لأننا نبحث عن قواعد تسجيل الدخول المرتبطة بمجموعة الأمان، والتي يمكن أن يكون هناك الكثير منها، ونطاقات CIDR في تلك القواعد، والتي يمكن أن يكون هناك الكثير منها أيضًا. لكننا تمكنا من:
// check 3: Не должно быть SSH, открытого в Интернет.
it("must not open port 22 (SSH) to the Internet", function(done) {
pulumi.all([ group.urn, group.ingress ]).apply(([ urn, ingress ]) => {
if (ingress.find(rule =>
rule.fromPort == 22 && rule.cidrBlocks.find(block =>
block === "0.0.0.0/0"))) {
done(new Error(`Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group ${urn}`));
} else {
done();
}
});
});
هذا كل شئ. الآن دعونا نجري الاختبارات!
تشغيل الاختبارات
في معظم الحالات، يمكنك إجراء الاختبارات بالطريقة المعتادة، باستخدام إطار الاختبار الذي تختاره. ولكن هناك ميزة واحدة في Pulumi تستحق الاهتمام بها.
عادةً، لتشغيل برامج Pulumi، يتم استخدام pulimi CLI (واجهة سطر الأوامر)، التي تقوم بتكوين وقت تشغيل اللغة، وتتحكم في تشغيل محرك Pulumi بحيث يمكن تسجيل العمليات باستخدام الموارد وإدراجها في الخطة، وما إلى ذلك. ومع ذلك، هناك مشكلة واحدة. عند التشغيل تحت سيطرة إطار الاختبار الخاص بك، لن يكون هناك اتصال بين CLI ومحرك Pulumi.
للتغلب على هذه المشكلة، نحتاج فقط إلى تحديد ما يلي:
- اسم المشروع الموجود في متغير البيئة
PULUMI_NODEJS_PROJECT
(أو بشكل أعمPULUMI__PROJECT для других языков).
اسم المكدس المحدد في متغير البيئةPULUMI_NODEJS_STACK
(أو بشكل أعمPULUMI__ STACK).
متغيرات تكوين المكدس الخاص بك. ويمكن الحصول عليها باستخدام متغير البيئةPULUMI_CONFIG
وتنسيقها هو خريطة JSON مع أزواج المفاتيح/القيمة.سيقوم البرنامج بإصدار تحذيرات تشير إلى أن الاتصال بـ CLI/المحرك غير متاح أثناء التنفيذ. يعد هذا أمرًا مهمًا لأن برنامجك لن ينشر أي شيء فعليًا وقد يكون مفاجئًا إذا لم يكن هذا هو ما كنت تنوي القيام به! لإخبار Pulumi أن هذا هو بالضبط ما تحتاجه، يمكنك التثبيت
PULUMI_TEST_MODE
вtrue
.تخيل أننا بحاجة إلى تحديد اسم المشروع في
my-ws
، اسم المكدسdev
ومنطقة AWSus-west-2
. سيبدو سطر الأوامر لتشغيل اختبارات Mocha كما يلي:$ PULUMI_TEST_MODE=true PULUMI_NODEJS_STACK="my-ws" PULUMI_NODEJS_PROJECT="dev" PULUMI_CONFIG='{ "aws:region": "us-west-2" }' mocha tests.js
القيام بذلك، كما هو متوقع، سيُظهر لنا أن لدينا ثلاثة اختبارات فاشلة!
Infrastructure #server 1) must have a name tag 2) must not use userData (use an AMI instead) #group 3) must not open port 22 (SSH) to the Internet 0 passing (17ms) 3 failing 1) Infrastructure #server must have a name tag: Error: Missing a name tag on server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 2) Infrastructure #server must not use userData (use an AMI instead): Error: Illegal use of userData on server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 3) Infrastructure #group must not open port 22 (SSH) to the Internet: Error: Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group
دعونا نصلح برنامجنا:
"use strict"; let aws = require("@pulumi/aws"); let group = new aws.ec2.SecurityGroup("web-secgrp", { ingress: [ { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }, ], }); let server = new aws.ec2.Instance("web-server-www", { tags: { "Name": "web-server-www" }, instanceType: "t2.micro", securityGroups: [ group.name ], // reference the group object above ami: "ami-c55673a0" // AMI for us-east-2 (Ohio), }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns;
ثم قم بإجراء الاختبارات مرة أخرى:
Infrastructure #server ✓ must have a name tag ✓ must not use userData (use an AMI instead) #group ✓ must not open port 22 (SSH) to the Internet 3 passing (16ms)
كل شيء سار على ما يرام... مرحى! ✓✓✓
هذا كل ما لدينا اليوم، لكننا سنتحدث عن اختبار النشر في الجزء الثاني من الترجمة 😉
المصدر: www.habr.com