_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
عبارة عن شوكة من spiff توفر امتدادًا متوافقًا لـ spiff بناءً على أحدث إصدار يقدم مجموعة غنية من الميزات الجديدة غير المتوفرة بعد في spiff. سيتم دمج جميع الإصلاحات التي يوفرها مشروع spiff الأصلي في spiff++ أيضًا. نظرًا لأنه لن تكون هناك طريقة للعودة إلى قاعدة مصدر spiff، فقد تم إنشاء مستودع spiff++ مستقل جديد لمواصلة تطوير spiff++.spiff هي أداة سطر أوامر ونظام قوالب YAML مختلط داخل المجال. في حين تقوم أنظمة القوالب العادية بمعالجة ملف قالب عن طريق استبدال تعبيرات القالب بقيم مأخوذة من مصادر بيانات خارجية، فإن "المجال الداخلي" يعني أن محرك القوالب يعرف بناء جملة القالب المعالج وبنيته. ولذلك يمكن أن يأخذ قيم تعبيرات القالب مباشرة من المستند الذي تمت معالجته، بما في ذلك تلك الأجزاء التي تشير إليها تعبيرات القالب نفسها.
على سبيل المثال:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
بدلاً من استخدام مصادر القيمة الخارجية فقط، يوفر spiff آلية دمج لدمج قالب مع أي عدد من بذرة الدمج لإنتاج مستند نهائي.
إنها أداة سطر أوامر ونظام قوالب YAML تعريفي، مصممة خصيصًا لإنشاء بيانات النشر (على سبيل المثال، بيانات BOSH أو Kubernetes أو Landscaper).
إلى جانب واجهة سطر الأوامر (CLI)، توجد مكتبة golang تتيح استخدام معالجة قالب spiff في أي برنامج GO (على سبيل المثال Landscaper).
يوفر محرك القوالب إمكانية الوصول إلى نظام الملفات بناءً على نظام ملفات افتراضي قابل للتكوين أو نظام العملية لتنفيذ الأوامر ودمج المخرجات في معالجة القالب.
محتويات:
<<if:
<<switch:
<<type:
<<for:
<<merge:
يمكن تنزيل الثنائيات القابلة للتنفيذ للإصدار الرسمي عبر إصدارات Github لأجهزة Darwin وLinux وPowerPC (والأجهزة الافتراضية).
لقد تغيرت بعض تبعيات spiff منذ الإصدار الرسمي الأخير، ولن يتم تحديث spiff لمواكبة هذه التبعيات. يتم إصلاح هذه التبعيات أو نسخها إلى قاعدة التعليمات البرمجية المحلية.
spiff merge template.yml [template2.yml ...]
قم بدمج مجموعة من ملفات القوالب في بيان واحد، ثم قم بطباعتها.
راجع "لغة قوالب dynaml" للحصول على تفاصيل ملف القالب، أو الأمثلة/الدير الفرعي للحصول على أمثلة أكثر تعقيدًا.
مثال:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
من الممكن قراءة ملف واحد من الإدخال القياسي باستخدام اسم الملف -
. ويمكن استخدامه مرة واحدة فقط. يسمح هذا باستخدام spiff كجزء من خط أنابيب لمعالجة دفق واحد فقط أو لمعالجة دفق بناءً على عدة قوالب/بذرة.
قد يكون ملف القالب (الوسيطة الأولى) عبارة عن دفق متعدد المستندات يحتوي على مستندات YAML متعددة مفصولة بخط يحتوي فقط على ---
. ستتم معالجة كل مستند YAML بشكل مستقل باستخدام ملفات كعب الروتين المحددة. والنتيجة هي تدفق المستندات المعالجة بنفس الترتيب. إذا تم وضع علامة على العقدة الجذرية للمستند على أنها مؤقتة، فسيتم حذف المستند من دفق الإخراج. على سبيل المثال، يمكن استخدام هذا لإنشاء بيانات kubernetes ليتم استخدامها بواسطة kubectl
.
يقدم أمر merge
عدة خيارات:
الخيار --partial
. إذا تم إعطاء هذا الخيار، فإن spiff يعالج تقييم التعبير غير المكتمل. يتم تجاهل كافة الأخطاء ويتم إرجاع الأجزاء غير القابلة للحل من مستند yaml كسلاسل.
باستخدام الخيار --json
سيكون الإخراج بتنسيق JSON بدلاً من YAML.
يمكن استخدام الخيار --path <path>
لإخراج مسار متداخل، بدلاً من المستند الذي تمت معالجته بالكامل.
إذا كان الإخراج عبارة عن قائمة، فإن الخيار --split
يقوم بإخراج كل عنصر من عناصر القائمة كمستند منفصل. يستخدم تنسيق yaml كالمعتاد ---
كخط فاصل. يقوم تنسيق json بإخراج سلسلة من مستندات json ، واحدة في كل سطر.
باستخدام --select <field path>
من الممكن تحديد حقل مخصص للمستند الذي تمت معالجته للإخراج
باستخدام --evaluate <dynaml expression>
من الممكن تقييم تعبير dynaml محدد في المستند الذي تمت معالجته للإخراج. يتم تقييم التعبير قبل تطبيق مسار التحديد، والذي سيعمل بعد ذلك على نتيجة التقييم.
يتيح الخيار --state <path>
دعم الحالة لـ spiff . إذا كان الملف المحدد موجودًا، فسيتم وضعه أعلى قائمة كعب الروتين التي تم تكوينها للملف المحدد الموجود، ويتم وضعه أعلى قائمة كعب الروتين التي تم تكوينها لمعالجة الدمج. بالإضافة إلى إخراج المستند الذي تمت معالجته، تتم تصفيته للعقد المميزة بعلامة &state
. يتم بعد ذلك تخزين هذا المستند الذي تمت تصفيته ضمن الملف المشار إليه، مع حفظ ملف الحالة القديم باللاحقة .bak
. يمكن استخدام هذا مع الدمج اليدوي كما هو معروض من قبل مكتبة المرافق الحكومية.
باستخدام الخيار --bindings <path>
يمكن تحديد ملف yaml، الذي يُستخدم محتواه لإنشاء روابط إضافية للمعالجة. يجب أن تتكون وثيقة yaml من خريطة. يتم استخدام كل مفتاح كملزم إضافي. لم تتم معالجة مستند الارتباطات، ويتم استخدام القيم كما هو محدد.
باستخدام الخيار --tag <tag>:<path>
يمكن تحديد ملف yaml، الذي يتم استخدام محتواه كقيمة لعلامة عالمية محددة مسبقًا (انظر العلامات). يمكن الوصول إلى العلامات من خلال التعبيرات المرجعية بالصيغة <tag>::<ref>
. وعلى النقيض من الارتباطات، لا يتنافس المحتوى الموسوم مع العقد الموجودة في المستند، بل يستخدم مساحة اسم مرجعية أخرى.
باستخدام الخيار --define <key>=<value>
(المختصر -D
) يمكن تحديد قيم ربط إضافية في سطر الأوامر لتجاوز قيم الربط من ملف الربط. قد يحدث الخيار عدة مرات.
إذا كان المفتاح يحتوي على نقاط ( .
)، فسيتم تفسيره على أنه تعبير مسار لوصف الحقول في قيم الخريطة العميقة. يمكن تخطي النقطة (و قبل النقطة) بواسطة
للاحتفاظ بها في اسم الحقل.
سيحافظ الخيار --preserve-escapes
على الهروب لتعبيرات dynaml وتوجيهات دمج القائمة/الخريطة. يمكن استخدام هذا الخيار إذا كان المقصود خطوات معالجة إضافية لنتيجة المعالجة باستخدام spiff .
الخيار --preserve-temporary
سوف يحافظ على الحقول المميزة كمؤقتة في الوثيقة النهائية.
سيؤدي الخيار --features=<featurelist>
إلى تمكين هذه الميزات المحددة. يجب تمكين الميزات الجديدة غير المتوافقة مع السلوك القديم بشكل صريح. عادةً لا تكسر هذه الميزات السلوك الشائع ولكنها تقدم تفسيرًا مخصصًا لقيم yaml التي تم استخدامها كقيم عادية من قبل.
توفر مكتبات المجلدات بعض المكتبات المساعدة المفيدة. ويمكن أيضًا استخدامها كمثال لقوة محرك القوالب هذا.
spiff diff manifest.yml other-manifest.yml
إظهار الاختلافات الهيكلية بين بياني النشر. هنا يتم دعم التدفقات التي تحتوي على مستندات متعددة أيضًا. للإشارة إلى عدم وجود اختلاف، يجب أن يكون عدد المستندات في كلا المسارين متطابقًا ويجب ألا يكون لكل مستند في التدفق الأول أي اختلاف مقارنة بالمستند الذي له نفس الفهرس في التدفق الثاني. يتم عرض الاختلافات التي تم العثور عليها لكل مستند على حدة.
على عكس أدوات الاختلاف الأساسية وحتى bosh diff
، يتمتع هذا الأمر بمعرفة دلالية لبيان النشر، ولا يعتمد على النص فقط. على سبيل المثال، إذا كان هناك بيانان متماثلان باستثناء أن لديهما بعض الوظائف مدرجة في أوامر مختلفة، فسوف يكتشف spiff diff
ذلك، نظرًا لأن أمر الوظيفة مهم في البيان. من ناحية أخرى، إذا كان هناك اختلاف بين بيانين فقط في ترتيب مجموعات الموارد الخاصة بهما، على سبيل المثال، فسوف يؤدي ذلك إلى اختلافات وإفراغها نظرًا لأن ترتيب تجمع الموارد لا يهم فعليًا لعملية النشر.
وعلى عكس bosh diff
أيضًا، لا يقوم هذا الأمر بتعديل أي من الملفين.
لقد تم تصميمه للتحقق من الاختلافات بين عملية نشر واحدة والأخرى.
التدفق النموذجي:
$ spiff merge template.yml [templates...] > deployment.yml
$ bosh download manifest [deployment] current.yml
$ spiff diff deployment.yml current.yml
$ bosh deployment deployment.yml
$ bosh deploy
spiff convert --json manifest.yml
يمكن استخدام أمر convert
الفرعي لتحويل ملفات الإدخال إلى json أو فقط لتطبيع ترتيب الحقول. الخيارات المتاحة هي --json
أو --path
أو --split
أو --select
وفقًا لمعانيها الخاصة بأمر merge
.
spiff encrypt secret.yaml
يمكن استخدام الأمر الفرعي encrypt
لتشفير البيانات أو فك تشفيرها وفقًا لوظيفة encrypt
dynaml. يمكن إعطاء كلمة المرور كوسيطة ثانية أو أنها مأخوذة من متغير البيئة SPIFF_ENCRYPTION_KEY
. يمكن استخدام الوسيطة الأخيرة لتمرير طريقة التشفير (انظر وظيفة encrypt
)
يتم أخذ البيانات من الملف المحدد. إذا -
معطى، تتم قراءته من stdin.
إذا تم إعطاء الخيار -d
، فسيتم فك تشفير البيانات، وإلا تتم قراءة البيانات كمستند yaml وتتم طباعة النتيجة المشفرة.
يجب تمكين الميزات الجديدة غير المتوافقة مع السلوك القديم بشكل صريح. عادةً، لا تكسر هذه الميزات السلوك الشائع ولكنها تقدم تفسيرًا مخصصًا لقيم yaml التي تم استخدامها كقيم عادية من قبل، وبالتالي يمكنها كسر حالات الاستخدام الحالية.
علامات الميزات التالية مدعومة حاليًا:
ميزة | منذ | ولاية | معنى |
---|---|---|---|
interpolation | 1.7.0-بيتا-1 | ألفا | dynaml كجزء من سلاسل yaml |
control | 1.7.0-بيتا-4 | ألفا | هياكل التحكم القائمة على yaml |
يمكن الاستعلام عن أعلام الميزات النشطة باستخدام دالة dynaml features()
كقائمة من السلاسل. إذا تم استدعاء هذه الوظيفة باستخدام وسيطة سلسلة، فإنها تُرجع ما إذا كانت الميزة المحددة ممكّنة حاليًا.
يمكن تمكين الميزات عن طريق سطر الأوامر باستخدام خيار --features
، أو عن طريق مكتبة go باستخدام وظيفة WithFeatures
أو بشكل عام عن طريق تعيين متغير البيئة SPIFF_FEATURES
إلى قائمة الميزات. يتم استخدام هذا الإعداد دائمًا كإعداد افتراضي. باستخدام إعدادات spiff Plain()
من مكتبة go، يتم تجاهل كافة متغيرات البيئة.
يمكن تحديد الميزة بالاسم أو بالاسم المضاف إليه البادئة no
" لتعطيلها.
يحتوي مجلد المكتبات على بعض مكتبات قوالب spiff المفيدة. هذه في الأساس مجرد بذرة تتم إضافتها إلى قائمة ملفات الدمج لتقديم وظائف الأداة المساعدة لمعالجة الدمج.
يستخدم Spiff لغة قوالب تعريفية خالية من المنطق تسمى "dynaml" (yaml الديناميكي).
يتم ضمان حل كل عقدة dynaml إلى عقدة YAML. إنه ليس استيفاء سلسلة. وهذا يمنع المطورين من التفكير في كيفية عرض القيمة في القالب الناتج.
تظهر عقدة dynaml في ملف .yml كسلسلة تشير إلى تعبير محاط بقوسين (( <dynaml> ))
. يمكن استخدامها كقيمة خريطة أو إدخال في القائمة. قد يمتد التعبير إلى عدة أسطر. على أية حال، يجب ألا تنتهي قيمة سلسلة yaml بسطر جديد (على سبيل المثال باستخدام |-
)
إذا لم يكن من الضروري تفسير القيمة الموجودة بين قوسين على أنها تعبير ديناميكي والاحتفاظ بها كما هي في الإخراج، فيمكن تجاوزها بعلامة تعجب مباشرة بعد الأقواس المفتوحة.
على سبيل المثال، يقوم ((! .field ))
بالتعيين إلى قيمة السلسلة (( .field ))
و ((!! .field ))
بالتعيين إلى قيمة السلسلة ((! .field ))
.
فيما يلي قائمة كاملة بتعبيرات الديناميكية:
(( foo ))
ابحث عن أقرب مفتاح 'foo' (أي النطاق المعجمي) في القالب الحالي وقم بإحضاره.
على سبيل المثال:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
سيحل هذا المثال إلى:
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
لن يتم حل ما يلي لأن اسم المفتاح هو نفس القيمة المراد دمجها:
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
ابحث عن أقرب مفتاح 'foo'، ومن هناك انتقل إلى .bar.[1].baz
.
المسار عبارة عن سلسلة من الخطوات مفصولة بالنقاط. الخطوة هي إما كلمة للخرائط، أو أرقام محاطة بأقواس لفهرسة القائمة. قد يكون الفهرس سالبًا (ناقص متبوعًا بالأرقام). يتم أخذ المؤشرات السلبية من نهاية القائمة (الفهرس الفعال = الفهرس + الطول (القائمة)).
المسار الذي لا يمكن حله يؤدي إلى خطأ في التقييم. إذا كان من المتوقع عدم توفير مرجع في بعض الأحيان، فيجب استخدامه مع '||' (انظر أدناه) لضمان الحل.
ملاحظة : تمت إعادة صياغة قواعد dynaml لتمكين بناء جملة الفهرس المعتاد الآن. بدلاً من foo.bar.[1]
من الممكن الآن استخدام foo.bar[1]
.
ملحوظة : المراجع تكون دائمًا داخل القالب أو كعب الروتين، ولا يهم الترتيب. يمكنك الرجوع إلى عقدة ديناميكية أخرى وافتراض أنه تم حلها، وسيتم حل العقدة المرجعية في النهاية بمجرد حل العقدة التابعة.
على سبيل المثال:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
سيتم حل هذا طالما أن "شيئًا ما" قابل للحل، وطالما أنه يجلب شيئًا مثل هذا:
from :
the :
stub : foo
إذا كان المسار يبدأ بنقطة ( .
) فسيتم تقييم المسار دائمًا من جذر المستند. إذا كان جذر المستند عبارة عن قائمة، فسيتم استخدام مستوى الخريطة الأول لحل تعبير المسار إذا كان يبدأ بـ .__map
. يمكن استخدام هذا لتجنب الحاجة إلى استخدام فهرس القائمة الخاص (مثل .[1].path
)، والذي قد يتغير إذا تمت إضافة إدخالات القائمة.
يمكن معالجة إدخالات القائمة التي تتكون من خريطة بحقل name
مباشرةً من خلال قيمة الاسم الخاصة بها كمكون المسار.
ملاحظة : يعمل هذا أيضًا مع المسارات المطلقة لمستندات القائمة.
على سبيل المثال:
عمر أليس في
list :
- name : alice
age : 25
يمكن الرجوع إليه باستخدام المسار list.alice.age
، بدلاً من list[0].age
.
بشكل افتراضي، يتم استخدام حقل name
كحقل مفتاح. إذا كان يجب استخدام حقل آخر كحقل رئيسي، فيمكن وضع علامة عليه في أحد إدخالات القائمة كمفتاح عن طريق وضع بادئة لاسم الحقل key:
. تتم إزالة هذه الكلمة الرئيسية من خلال المعالجة ولن تكون جزءًا من نتيجة المعالجة النهائية.
على سبيل المثال:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
سيتم حل ل
list :
- person : alice
age : 25
alice :
person : alice
age : 25
سيتم أيضًا ملاحظة هذا الحقل الرئيسي الجديد أثناء دمج القوائم.
إذا كان حقل المفتاح المحدد يبدأ بـ !
، الميزة الرئيسية معطلة. تتم إزالة علامة التعجب من اسم الحقل الفعال أيضًا.
إذا كانت قيم حقل المفتاح غير ثابتة، فسيتم تعطيلها أيضًا.
(( foo.[bar].baz ))
ابحث عن أقرب مفتاح 'foo'، ومن هناك تابع إلى الحقل (الحقول) الموضح في bar
التعبير ثم إلى .baz.
قد يكون الفهرس عددًا صحيحًا ثابتًا (بدون مسافات) كما هو موضح في القسم الأخير. ولكنه قد يكون أيضًا تعبيرًا ديناميكيًا عشوائيًا (حتى لو كان عددًا صحيحًا، ولكن بمسافات). إذا تم تقييم التعبير إلى سلسلة، فإنه يبحث في الحقل المخصص. إذا تم تقييم التعبير إلى عدد صحيح، فسيتم تناول عنصر الصفيف مع هذا الفهرس. النقطة ( .
) أمام عامل الفهرس اختيارية.
على سبيل المثال:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
سيؤدي هذا إلى حل foo
إلى القيمة 42
. قد يكون الفهرس الديناميكي أيضًا في نهاية التعبير (بدون .bar
).
في الأساس، هذه هي الطريقة الأبسط للتعبير عن شيء مثل eval("values." name ".bar")
إذا تم تقييم التعبير إلى قائمة، فسيتم استخدام عناصر القائمة (سلاسل أو أعداد صحيحة) كعناصر مسار للوصول إلى حقول أعمق.
على سبيل المثال:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
يحل foo
مرة أخرى إلى القيمة 42
.
ملاحظة : يمكن استخدام عامل الفهرس على العنصر الجذر ( .[index]
) أيضًا.
من الممكن تحديد مؤشرات متعددة مفصولة بفواصل لقوائم متتالية ( foo[0][1]
يعادل `foo[0,1]). في مثل هذه الحالة قد لا تكون المؤشرات قوائم مرة أخرى.
(( list.[1..3] ))
يمكن استخدام تعبير الشريحة لاستخراج قائمة فرعية مخصصة من تعبير القائمة. النطاق start ..
end يستخرج قائمة بالطول end-start+1 مع العناصر من الفهرس start to end . إذا كان مؤشر البداية سالبًا، يتم أخذ الشريحة من نهاية القائمة من length+start إلى length+end . إذا كان فهرس النهاية أقل من فهرس البداية، فستكون النتيجة مصفوفة فارغة.
على سبيل المثال:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
قد يتم حذف فهرس البداية أو النهاية. ومن ثم يتم تحديده وفقًا للحجم الفعلي للقائمة. لذلك فإن list.[1..length(list)]
تعادل list.[1..]
.
يقيم foo
إلى القائمة [b,c]
.
(( 1.2e4 ))
يتم دعم الأرقام الحرفية للأعداد الصحيحة وقيم الفاصلة العائمة.
(( "foo" ))
سلسلة حرفية. جميع ترميزات سلسلة json مدعومة (على سبيل المثال n
أو "
أو uxxxx
).
(( [ 1, 2, 3 ] ))
القائمة حرفية. قد تكون عناصر القائمة تعبيرات مرة أخرى. توجد قائمة حرفية خاصة [1 .. -1]
يمكن استخدامها لحل نطاق الأرقام المتزايد أو المتناقص في القائمة.
على سبيل المثال:
list : (( [ 1 .. -1 ] ))
الغلة
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
يمكن استخدام الخريطة الحرفية لوصف الخرائط كجزء من تعبير ديناميكي. قد يكون كل من المفتاح والقيمة عبارة عن تعبيرات مرة أخرى، حيث يجب تقييم تعبير المفتاح إلى سلسلة. بهذه الطريقة من الممكن إنشاء خرائط بمفاتيح غير ثابتة. تم اختيار عامل التعيين =
بدلاً من حرف النقطتين العاديين :
المستخدم في yaml، لأن هذا قد يؤدي إلى تعارض مع بناء جملة yaml.
قد تتكون الخريطة الحرفية من أي عدد من تعيينات الحقول مفصولة بفاصلة ,
على سبيل المثال:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
الغلة
name : peter
age : 23
map :
alice : {}
peter : 23
هناك طريقة أخرى لإنشاء قوائم بناءً على التعبيرات وهي الوظائف makemap
و list_to_map
.
(( ( "alice" = 25 ) alice ))
يمكن أن يُسبق أي تعبير بأي عدد من القيم الحرفية الصريحة للنطاق . يصف النطاق الحرفي خريطة تتوفر قيمها للتحليل المرجعي النسبي للتعبير (النطاق الثابت). يقوم بإنشاء ربط محلي إضافي للأسماء المحددة.
قد يتكون النطاق الحرفي من أي عدد من تعيينات الحقول مفصولة بفاصلة ,
يتم إعطاء المفتاح وكذلك القيمة من خلال التعبيرات، في حين يجب تقييم التعبير الرئيسي إلى سلسلة. يتم تقييم كافة التعبيرات في النطاق الخارجي التالي، وهذا يعني أن الإعدادات اللاحقة في النطاق لا يمكنها استخدام الإعدادات السابقة في نفس النطاق الحرفي.
على سبيل المثال:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
الغلة
scoped : 51
يمكن أيضًا الإشارة إلى اسم الحقل بالرمز ( $
name ).
(( foo bar ))
تعبير التسلسل يستخدم لتسلسل سلسلة من تعبيرات الديناميكية.
(( "foo" bar ))
التسلسل (حيث يكون الشريط هو dynaml expr آخر). يمكن تسلسل أي تسلسلات من القيم البسيطة (سلسلة، وعدد صحيح، ومنطقي)، من خلال أي تعبير ديناميكي.
على سبيل المثال:
domain : example.com
uri : (( "https://" domain ))
في هذا المثال، سيتم تحويل uri
إلى القيمة "https://example.com"
.
(( [1,2] bar ))
تسلسل القوائم كتعبير (حيث يكون الشريط هو dynaml expr آخر). يمكن تسلسل أي تسلسلات من القوائم، عن طريق أي تعبير ديناميكي.
على سبيل المثال:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
في هذا المثال، سيتم حل static_ips
إلى القيمة [ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
.
إذا تم تقييم التعبير الثاني إلى قيمة أخرى غير القائمة (عدد صحيح أو منطقي أو سلسلة أو خريطة)، فسيتم إلحاق القيمة بالقائمة الأولى.
على سبيل المثال:
foo : 3
bar : (( [1] 2 foo "alice" ))
ينتج القائمة [ 1, 2, 3, "alice" ]
bar
.
(( map1 map2 ))
تسلسل الخرائط كتعبير. يمكن تسلسل أي تسلسلات من الخرائط، عن طريق أي تعبير ديناميكي. وبالتالي سيتم دمج الإدخالات. تتم الكتابة فوق الإدخالات التي لها نفس المفتاح من اليسار إلى اليمين.
على سبيل المثال:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
الغلة
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
حساب القيمة التلقائية الحساسة للسياق.
في سمة "الحجم" الخاصة بتجمع الموارد، يعني هذا الحساب استنادًا إلى إجمالي مثيلات جميع الوظائف التي تعلن أنها موجودة في تجمع الموارد الحالي.
على سبيل المثال:
resource_pools :
- name : mypool
size : (( auto ))
jobs :
- name : myjob
resource_pool : mypool
instances : 2
- name : myotherjob
resource_pool : mypool
instances : 3
- name : yetanotherjob
resource_pool : otherpool
instances : 3
في هذه الحالة، سيتم تحويل حجم تجمع الموارد إلى "5".
(( merge ))
قم بإحضار المسار الحالي من ملفات كعب الروتين التي يتم دمجها.
على سبيل المثال:
foo :
bar :
baz : (( merge ))
سنحاول جلب foo.bar.baz
من كعب الروتين الأول، أو الثاني، وما إلى ذلك، وإرجاع القيمة من كعب الروتين الأخير الذي يوفرها.
إذا لم يتم تعريف القيمة المقابلة، فسوف يُرجع nil. وهذا له نفس دلالات التعبيرات المرجعية؛ الدمج الصفري هو قالب لم يتم حله. انظر ||
.
<<: (( merge ))
دمج الخرائط أو القوائم مع محتوى نفس العنصر الموجود في بعض العقب.
** انتبه ** هذا النوع من merge
به مشكلة توافق. في الإصدارات قبل 1.0.8، لم يتم تحليل هذا التعبير مطلقًا، فقط وجود المفتاح <<:
كان ذا صلة. ولذلك غالبًا ما تكون هناك استخدامات لـ <<: (( merge ))
حيث يُقصد <<: (( merge || nil ))
. سيتطلب المتغير الأول محتوى في كعب روتين واحد على الأقل (كما هو الحال دائمًا بالنسبة لمشغل الدمج). الآن تم تقييم هذا التعبير بشكل صحيح، ولكن هذا من شأنه أن يؤدي إلى كسر مجموعات قوالب البيان الموجودة، والتي تستخدم المتغير الأول، ولكنها تعني المتغير الثاني. لذلك تتم معالجة هذه الحالة بشكل صريح لوصف عملية دمج اختيارية. إذا كان المقصود من الدمج المطلوب حقًا هو وجود مؤهل صريح إضافي
ملاحظة : بدلاً من استخدام حقل <<:
إدراج لوضع تعبيرات الدمج، من الممكن الآن استخدام <<<:
والذي يسمح أيضًا باستخدام محللي yaml العاديين لمستندات yaml الشبيهة بـ spiff. <<:
يتم الاحتفاظ به للتوافق مع الإصدارات السابقة. سيتم استخدامه ( (( merge required ))
).
إذا لم يكن من الضروري تفسير مفتاح الدمج كمفتاح عادي بدلاً من توجيه دمج، فيمكن الهروب منه بعلامة تعجب ( !
).
على سبيل المثال، مفتاح الخريطة <<<!
سينتج عنه مفتاح سلسلة <<<
و <<<!!
سينتج عنه مفتاح سلسلة <<<!
value.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
العائدات:
foo :
a : 1
b : 2
c : 4
value.yml
foo :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
العائدات:
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
قادر على دمج قوائم الخرائط مع حقل رئيسي. يتم التعامل مع هذه القوائم مثل الخرائط مع قيمة حقل المفتاح كمفتاح. بشكل افتراضي يتم استخدام name
المفتاح. ولكن on
المحدد، يمكن تحديد اسم مفتاح عشوائي لتعبير دمج القائمة.
على سبيل المثال:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
اندمجت مع
list :
- key : alice
age : 20
- key : peter
age : 13
الغلة
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
إذا لم تكن هناك رغبة في إدراج إدخالات جديدة (كما هو مطلوب بواسطة تعبير دمج الإدراج)، ولكن فقط تجاوز الإدخالات الموجودة، فيمكن إضافة حقل رئيسي موجود ببادئة key:
للإشارة إلى اسم مفتاح غير قياسي، على سبيل المثال - key:key: alice
.
<<: (( merge replace ))
يستبدل المحتوى الكامل لعنصر بالمحتوى الموجود في بعض كعب الروتين بدلاً من إجراء دمج عميق للمحتوى الموجود.
value.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
العائدات:
foo :
a : 1
b : 2
value.yml
foo :
- 1
- 2
template.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
العائدات:
foo :
- 1
- 2
<<: (( foo ))
دمج الخرائط والقوائم الموجودة في نفس القالب أو كعب الروتين.
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
الغلة:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
يضيف هذا التعبير فقط إدخالات جديدة إلى القائمة الفعلية. ولا يقوم بدمج الإدخالات الموجودة مع المحتوى الموصوف بواسطة تعبير الدمج.
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
الغلة:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
إحدى حالات الاستخدام الشائعة لذلك هي دمج قوائم عناوين IP الثابتة أو النطاقات في قائمة عناوين IP. الاحتمال الآخر هو استخدام تعبير تسلسلي واحد.
<<: (( merge foo ))
دمج الخرائط أو القوائم مع محتوى عنصر عشوائي موجود في بعض كعب الروتين (إعادة توجيه الدمج). لن يكون هناك المزيد من الدمج (العميق) مع العنصر الذي يحمل نفس الاسم الموجود في بعض كعب الروتين. (يتطلب الدمج العميق للقوائم خرائط مع name
الحقل)
يمكن استخدام إعادة توجيه عمليات الدمج كقيمة حقل مباشرة أيضًا. ويمكن دمجها مع عمليات الدمج البديلة مثل (( merge replace foo ))
.
value.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
العائدات:
foo :
a : 1
b : 2
c : 4
هناك طريقة أخرى لإجراء الدمج مع عنصر آخر في بعض كعب الروتين ويمكن أيضًا إجراؤها بالطريقة التقليدية:
value.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
ولكن في هذا السيناريو، لا يزال الدمج يقوم بإجراء الدمج العميق مع اسم العنصر الأصلي. ولذلك spiff merge template.yml values.yml
ينتج عنه:
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
value.yml
foo :
- 10
- 20
bar :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
العائدات:
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
إذا تم تعيين مرجع دمج إعادة التوجيه على القيمة الثابتة none
، فلن يتم إجراء أي دمج على الإطلاق. هذه التعبيرات تعطي دائمًا القيمة الصفرية.
على سبيل المثال: ل
template.yml
map :
<< : (( merge none ))
value : notmerged
value.yml
map :
value : merged
spiff merge template.yml values.yml
العائدات:
map :
value : notmerged
يمكن استخدام هذا لدمج الحقول بشكل صريح باستخدام وظيفة stub
للوصول إلى الأجزاء المخصصة من كعب الروتين.
على سبيل المثال:
template.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
value.yml
map :
value : bob
spiff merge template.yml values.yml
العائدات:
test :
value : alice+bob
يعمل هذا أيضًا مع المجالات المخصصة:
template.yml
map :
value : (( merge none // "alice" "+" stub() ))
value.yml
map :
value : bob
spiff merge template.yml values.yml
العائدات:
test :
value : alice+bob
(( a || b ))
يستخدم a، أو b إذا تعذر حل a.
على سبيل المثال:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
سيحاول هذا الدمج في mything.complicated_structure
، أو إذا لم يكن من الممكن دمجه، فاستخدم الإعداد الافتراضي المحدد في foo.bar
.
يقوم عامل التشغيل //
بالإضافة إلى ذلك بالتحقق مما إذا كان يمكن حل a
بقيمة صالحة (لا تساوي ~
).
(( 1 + 2 * foo ))
يمكن استخدام تعبيرات Dynaml لتنفيذ العمليات الحسابية الصحيحة والفاصلة العائمة. العمليات المدعومة هي +
و -
و *
و /
. مشغل modulo ( %
) يدعم فقط المعاملات الصحيحة.
على سبيل المثال:
value.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
ينتج 7
bar
. يمكن دمج ذلك مع التسلسلات (الحساب له أولوية أعلى من التسلسل في تعبيرات الديناميكية):
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
والنتيجة هي السلسلة 3 times 2 yields 6
.
(( "10.10.10.10" - 11 ))
إلى جانب العمليات الحسابية على الأعداد الصحيحة، من الممكن أيضًا استخدام الجمع والطرح على عناوين IP وcidrs.
على سبيل المثال:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
الغلة
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
يعمل الطرح أيضًا على عنواني IP أو cidrs لحساب عدد عناوين IP بين عنواني IP.
على سبيل المثال:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
يعطي القيمة 256. يمكن استخدام ثوابت عنوان IP مباشرة في تعبيرات dynaml. ويتم تحويلها ضمنيًا إلى سلاسل وإعادتها إلى عناوين IP إذا تطلبت العملية ذلك.
يمكن استخدام الضرب والقسمة للتعامل مع تحولات نطاق IP على CIDRs. مع التقسيم يمكن تقسيم الشبكة. يتم زيادة حجم الشبكة للسماح بعدد مخصص على الأقل من الشبكات الفرعية أسفل CIDR الأصلي. يمكن بعد ذلك استخدام الضرب للحصول على الشبكة الفرعية التالية من نفس الحجم.
على سبيل المثال:
subnet : (( "10.1.2.1/24" / 12 )) # first subnet CIDR for 16 subnets
next : (( "10.1.2.1/24" / 12 * 2)) # 2nd next (3rd) subnet CIDRS
الغلة
subnet : 10.1.2.0/28
next : 10.1.2.32/28
بالإضافة إلى ذلك، هناك وظائف تعمل على IPv4 CIDRs:
cidr : 192.168.0.1/24
range : (( min_ip(cidr) "-" max_ip(cidr) ))
next : (( max_ip(cidr) + 1 ))
num : (( min_ip(cidr) "+" num_ip(cidr) "=" min_ip(cidr) + num_ip(cidr) ))
contains : (( contains_ip(cidr, "192.168.0.2") ))
الغلة
cidr : 192.168.0.1/24
range : 192.168.0.0-192.168.0.255
next : 192.168.1.0
num : 192.168.0.0+256=192.168.1.0
contains : true
(( a > 1 ? foo :bar ))
يدعم Dynaml عوامل المقارنة <
, <=
, ==
, !=
, >=
و >
. تعمل عوامل المقارنة على قيم عددية. تعمل عمليات التحقق من المساواة أيضًا على القوائم والخرائط. والنتيجة هي دائما قيمة منطقية. لإبطال شرط يمكن استخدام عامل التشغيل الأحادي غير ( !
) .
بالإضافة إلى ذلك، هناك العامل الشرطي الثلاثي ?:
، الذي يمكن استخدامه لتقييم التعبيرات اعتمادًا على الشرط. يتم استخدام المعامل الأول كشرط. يتم تقييم التعبير إلى المعامل الثاني، إذا كان الشرط صحيحا، وإلى المعامل الثالث، خلاف ذلك.
على سبيل المثال:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
يعطي قيمة bob
name
الخاصية.
يعتبر التعبير false
إذا تم تقييمه
false
وإلا فإنه يعتبر true
ملاحظة
قد يتعارض استخدام الرمز :
مع بناء جملة yaml، إذا لم يكن التعبير الكامل قيمة سلسلة مقتبسة.
يمكن استخدام عوامل التشغيل -or
و -and
لدمج عوامل المقارنة لتكوين شروط أكثر تعقيدًا.
ملاحظة:
رمز المشغل الأكثر تقليدية ||
(و &&
) لا يمكن استخدامها هنا، لأن عامل التشغيل ||
موجود بالفعل في dynaml مع دلالات مختلفة، لا تنطبق على العمليات المنطقية. التعبير false || true
يتم تقييم false || true
إلى false
، لأنها تنتج المعامل الأول، إذا تم تعريفه، بغض النظر عن قيمته. ولكي يكون هذا متوافقًا قدر الإمكان، لا يمكن تغييره ولا يمكن استخدام الرموز المجردة or
and
، لأن هذا من شأنه أن يبطل تسلسل المراجع مع هذه الأسماء.
(( 5 -or 6 ))
إذا تم تقييم كلا الجانبين من عامل التشغيل -or
أو -and
إلى قيم عددية، فسيتم تنفيذ عملية بت والنتيجة هي عدد صحيح مرة أخرى. لذلك يتم تقييم التعبير 5 -or 6
إلى 7
.
يدعم Dynaml مجموعة من الوظائف المحددة مسبقًا. تسمى الوظيفة عمومًا مثل
result : (( functionname(arg, arg, ...) ))
يمكن تعريف وظائف إضافية كجزء من وثيقة yaml باستخدام تعبيرات لامدا. يكون اسم الوظيفة إذن إما تعبيرًا مجمعًا أو المسار إلى العقدة التي تستضيف تعبير لامدا.
(( format( "%s %d", alice, 25) ))
قم بتنسيق سلسلة بناءً على الوسائط المقدمة بواسطة تعبيرات dynaml. هناك نكهة ثانية لهذه الوظيفة: error
يقوم بتنسيق رسالة خطأ ويضبط التقييم على فشل.
(( join( ", ", list) ))
انضم إلى إدخالات القوائم أو قم بتوجيه القيم إلى قيمة سلسلة واحدة باستخدام سلسلة فاصلة معينة. يمكن أن تكون الوسائط المراد الانضمام إليها عبارة عن تعبيرات ديناميكية يتم تقييمها إلى قوائم، تكون قيمها مرة أخرى عبارة عن سلاسل أو أعداد صحيحة، أو قيم سلسلة أو أعداد صحيحة.
على سبيل المثال:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
يعطي قيمة السلسلة bob, foo, bar, alice, 10
لـ join
.
(( split( ",", string) ))
تقسيم سلسلة لفاصل مخصص. والنتيجة هي قائمة. بدلاً من سلسلة فاصلة، قد يتم إعطاء قيمة عددية، والتي تقسم سلسلة العطاء إلى قائمة من السلاسل محدودة الطول. يتم حساب الطول بالرونية وليس بالبايت.
على سبيل المثال:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
الغلة:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
يمكن تحديد وسيطة ثالثة اختيارية. فهو يحد من عدد إدخالات القائمة التي تم إرجاعها. القيمة -1 تؤدي إلى طول قائمة غير محدود.
إذا كان يجب استخدام تعبير عادي كسلسلة فاصلة، فيمكن استخدام الدالة split_match
.
(( trim(string) ))
اقتطاع سلسلة أو جميع عناصر قائمة السلاسل. توجد وسيطة سلسلة ثانية اختيارية. ويمكن استخدامه لتحديد مجموعة من الأحرف التي سيتم قطعها. تتكون مجموعة القطع الافتراضية من مسافة وحرف جدولة.
على سبيل المثال:
list : (( trim(split("," "alice, bob")) ))
الغلة:
list :
- alice
- bob
(( element(list, index) ))
قم بإرجاع عنصر قائمة مخصص يقدمه الفهرس الخاص به.
على سبيل المثال:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
الغلة:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
قم بإرجاع حقل خريطة مخصص بواسطة مفتاحه.
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
الغلة:
map :
alice : 24
bob : 25
elem : 25
هذه الوظيفة قادرة أيضًا على التعامل مع المفاتيح التي تحتوي على نقاط (.).
(( compact(list) ))
تصفية قائمة تحذف الإدخالات الفارغة.
على سبيل المثال:
list : (( compact(trim(split("," "alice, , bob"))) ))
الغلة:
list :
- alice
- bob
(( uniq(list) ))
توفر Uniq قائمة خالية من التكرارات.
على سبيل المثال:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
عوائد الحقل uniq
:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
التحقق مما إذا كانت القائمة تحتوي على قيمة مخصصة. قد تكون القيم أيضًا عبارة عن قوائم أو خرائط.
على سبيل المثال:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
الغلة:
list :
- foo
- bar
- foobar
contains : true
contains
الوظيفة أيضًا على سلاسل للبحث عن سلاسل فرعية أو خرائط للبحث عن مفتاح. في تلك الحالات، يجب أن يكون العنصر عبارة عن سلسلة.
على سبيل المثال:
contains : (( contains("foobar", "bar") ))
ينتج true
.
(( basename(path) ))
يقوم basename
للوظيفة بإرجاع اسم العنصر الأخير في المسار. قد تكون الوسيطة إما اسم مسار عادي أو عنوان URL.
على سبيل المثال:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
الغلة:
pathbase : bob
urlbase : bob
(( dirname(path) ))
تقوم الدالة dirname
بإرجاع الدليل الأصلي للمسار. قد تكون الوسيطة إما اسم مسار عادي أو عنوان URL.
على سبيل المثال:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
الغلة:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
تقوم هذه الوظيفة بتوزيع عنوان URL وتنتج خريطة تحتوي على جميع عناصر عنوان URL. تعد حقول port
userinfo
وكلمة password
اختيارية.
على سبيل المثال:
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
الغلة:
url :
scheme : https
host : github.com
port : 443
path : /mandelsoft/spiff
fragment : anchor
query : branch=master&tag=v1
values :
branch : [ master ]
tag : [ v1 ]
userinfo :
username : user
password : pass
(( index(list, "foobar") ))
يتحقق مما إذا كانت القائمة تحتوي على قيمة مخصصة وتُرجع فهرس المباراة الأولى. قد تكون القيم أيضًا قوائم أو خرائط. إذا لم يتم العثور على أي إدخال -1
.
على سبيل المثال:
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
غلة:
list :
- foo
- bar
- foobar
index : 2
يعمل index
الوظائف أيضًا على السلاسل للبحث عن السلاسل الفرعية.
على سبيل المثال:
index : (( index("foobar", "bar") ))
عائدات 3
.
(( lastindex(list, "foobar") ))
تعمل الوظيفة lastindex
مثل index
ولكن يتم إرجاع فهرس آخر حدث.
يمكن استخدام sort
الوظيفة لفرز قوائم عدد صحيح أو سلسلة. عملية الفرز مستقرة.
على سبيل المثال:
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
عائدات sorted
- alice
- bob
- foobar
إذا كان ينبغي فرز أنواع أخرى ، خاصة الأنواع المعقدة مثل القوائم أو الخرائط ، أو مطلوب قاعدة مقارنة مختلفة ، يمكن تحديد وظيفة المقارنة كوسيطة ثانية اختيارية. يجب أن تكون وظيفة المقارنة تعبيرًا lambda يأخذ وسيطتين. يجب أن يكون نوع النتيجة integer
أو bool
يشير إلى ما إذا كان A أقل من ب . إذا تم إرجاع عدد صحيح ، فيجب أن يكون
على سبيل المثال:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
عائدات sorted
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
استبدل جميع حالات السلسلة الفرعية في سلسلة بسلسلة بديلة. مع وسيطة عدد صحيح اختياري ، يمكن أن يكون عدد البدائل محدودًا (-1 يعني غير محدود).
على سبيل المثال:
string : (( replace("foobar", "o", "u") ))
عائدات fuubar
.
إذا كان ينبغي استخدام تعبير منتظم كسلسلة بحث ، فيمكن استخدام الوظيفة replace_match
. هنا يتم تقييم سلسلة البحث كتعبير منتظم. قد conatain التعبيرات الفرعية. يمكن استخدام هذه المباريات في سلسلة الاستبدال
على سبيل المثال:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
غلة fbooar
.
قد تكون وسيطة الاستبدال أيضًا وظيفة Lambda. في هذه الحالة ، لكل مطابقة ، يتم استدعاء الوظيفة لتحديد قيمة الاستبدال. وسيطة الإدخال الفردية هي قائمة بمطابقات التعبير الفرعي الفعلي.
على سبيل المثال:
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
عائدات fOObar-barfoo
.
(( substr(string, 1, 2) ))
استخراج سلسلة كعب من السلسلة ، بدءًا من فهرس بدء معين حتى فهرس نهاية اختياري (حصري). إذا لم يتم إعطاء فهرس نهائي ، يتم استخراج struvt الفرعي حتى نهاية السلسلة. قد يكون كل من المؤشرات سلبية. في هذه الحالة ، يتم أخذها من نهاية السلسلة.
على سبيل المثال:
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
تقييم ل
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
إرجاع تطابق التعبير العادي لقيمة سلسلة معينة. المباراة هي قائمة بالقيم المتطابقة للتعبيرات الفرعية الواردة في التعبير العادي. يشير الفهرس 0 إلى تطابق التعبير العادي الكامل. إذا كانت قيمة السلسلة لا تتطابق مع قائمة فارغة.
على سبيل المثال:
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
غلة:
matches :
- foobar
- foo
- bar
قد يتم إعطاء حجة ثالثة من عدد صحيح من النوع لطلب مباراة متعددة بحد أقصى من التكرار n . إذا كانت القيمة سلبية يتم الإبلاغ عن جميع التكرار. والنتيجة هي قائمة بجميع المباريات ، كل بالتنسيق الموضح أعلاه.
(( keys(map) ))
تحديد قائمة الفرز للمفاتيح المستخدمة في الخريطة.
على سبيل المثال:
map :
alice : 25
bob : 25
keys : (( keys(map) ))
غلة:
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
حدد طول القائمة أو الخريطة أو قيمة السلسلة.
على سبيل المثال:
list :
- alice
- bob
length : (( length(list) ))
غلة:
list :
- alice
- bob
length : 2
(( base64(string) ))
يقوم base64
بإنشاء ترميز BASE64 لسلسلة معينة. base64_decode
يفكّر سلسلة مشفرة BASE64.
على سبيل المثال:
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
تقييم ل
base54 : dGVzdA==
test : test
يمكن استخدام وسيطة ثانية اختيارية لتحديد طول الخط القصوى. في هذه الحالة ، ستكون النتيجة سلسلة متعددة الخطوط.
(( hash(string) ))
hash
الوظيفة يولد عدة أنواع من التجزئة للسلسلة المحددة. بشكل افتراضي كما يتم إنشاء تجزئة sha256
. تحدد الوسيطة الثانية الاختيارية نوع التجزئة. الأنواع المحتملة هي md4
، md5
، sha1
، sha224
، sha256
، sha384
، sha2512
، sha512/224
أو sha512/256
.
لا يزال من الممكن إنشاء تجزئة md5
بواسطة Finctio md5(string)
.
على سبيل المثال:
data : alice
hash :
deprecated : (( md5(data) ))
md4 : (( hash(data,"md4") ))
md5 : (( hash(data,"md5") ))
sha1 : (( hash(data,"sha1") ))
sha224 : (( hash(data,"sha224") ))
sha256 : (( hash(data,"sha256") ))
sha384 : (( hash(data,"sha384") ))
sha512 : (( hash(data,"sha512") ))
sha512_224 : (( hash(data,"sha512/224") ))
sha512_256 : (( hash(data,"sha512/256") ))
تقييم ل
data : alice
hash :
deprecated : 6384e2b2184bcbf58eccf10ca7a6563c
md4 : 616c69636531d6cfe0d16ae931b73c59d7e0c089c0
md5 : 6384e2b2184bcbf58eccf10ca7a6563c
sha1 : 522b276a356bdf39013dfabea2cd43e141ecc9e8
sha224 : 38b7e5d5651aaf85694a7a7c6d5db1275af86a6df93a36b8a4a2e771
sha256 : 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90
sha384 : 96a5353e625adc003a01bdcd9b21b21189bdd9806851829f45b81d3dfc6721ee21f6e0e98c4dd63bc559f66c7a74233a
sha512 : 408b27d3097eea5a46bf2ab6433a7234a33d5e49957b13ec7acc2ca08e1a13c75272c90c8d3385d47ede5420a7a9623aad817d9f8a70bd100a0acea7400daa59
sha512_224 : c3b8cfaa37ae15922adf3d21606e3a9836ba2a9d7838b040b7c96fd7
sha512_256 : ad0a339b08dc090fe3b16eae376f7e162836e8728da9c45466842e19508d7627
(( bcrypt("password", 10) ))
تقوم الدالة bcrypt
بإنشاء تجزئة كلمة مرور BCrypt للسلسلة المحددة باستخدام عامل التكلفة المحدد (تم التخلف عن الافتراضي إلى 10 ، إذا فقدت).
على سبيل المثال:
hash : (( bcrypt("password", 10) ))
تقييم ل
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
تقوم الدالة bcrypt_check
بالتحقق من صحة كلمة مرور مقابل تجزئة BCrypt معينة.
على سبيل المثال:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
تقييم ل
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
تقوم الدالة md5crypt
بإنشاء تجزئة كلمة مرور Apache MD5 المشفرة للسلسلة المحددة.
على سبيل المثال:
hash : (( md5crypt("password") ))
تقييم ل
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
تقوم الدالة md5crypt_check
بالتحقق من صحة كلمة مرور مقابل تجزئة Apache MD5 المعينة المشفرة.
على سبيل المثال:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
تقييم ل
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
يمكن استخدام هذه الوظيفة لتخزين الأسرار المشفرة في ملف spiff Yaml. ستتضمن النتيجة المعالجة بعد ذلك القيمة التي تم فك تشفيرها. يمكن تشفير جميع أنواع العقدة وفك تشفيرها ، بما في ذلك الخرائط والقوائم الكاملة.
يمكن إعطاء كلمة المرور الخاصة بفك التشفير كوسيطة ثانية ، أو (الطريقة المفضلة) يمكن تحديدها بواسطة متغير البيئة SPIFF_ENCRYPTION_KEY
.
قد تحدد الوسيطة الأخيرة الاختيارية طريقة التشفير. الطريقة الوحيدة المدعومة حتى الآن هي 3DES
. يمكن إضافة طرق أخرى لإصدارات SPIFF المخصصة باستخدام تسجيل طريقة التشفير التي تقدمها مكتبة Spiff.
يمكن تشفير القيمة باستخدام وظيفة encrypt("secret")
.
على سبيل المثال:
password : this a very secret secret and may never be exposed to unauthorized people
encrypted : (( encrypt("spiff is a cool tool", password) ))
decrypted : (( decrypt(encrypted, password) ))
تم تقييمه إلى شيء مثل
decrypted : spiff is a cool tool
encrypted : d889f9e4cc7ae13effcbc8bb8cd0c38d1fb2197738444f753c48796d7946083e6639e5a1bf8f77648f2a1ddf37023c65ff57d52d0519d1d92cbcf87d3e263cba
password : this a very secret secret and may never be exposed to unauthorized people
(( rand("[:alnum:]", 10) ))
الدالة rand
يولد قيم عشوائية. تقرر الحجة الأولى نوع القيم المطلوبة. مع عدم وجود حجة ، فإنه يولد رقمًا عشوائيًا إيجابيًا في نطاق int64
.
نوع الحجة | نتيجة |
---|---|
كثافة العمليات | قيمة عدد صحيح في النطاق [0 ، n ) لـ N و ( n ، 0] للسلبية n |
منطقي | قيمة منطقية |
خيط | سلسلة Rune واحدة ، حيث يكون Rune في نطاق الأحرف المعطى ، يمكن استخدام أي مجموعة من فئات الأحرف أو نطاقات الأحرف القابلة للاستخدام في regexp. إذا تم تحديد وسيطة طول إضافية ، فستكون السلسلة الناتجة ذات الطول المحدد. |
على سبيل المثال:
int : (( rand() ))
int10 : (( rand(10) ))
neg10 : (( rand(-10) ))
bool : (( rand(true) ))
string : (( rand("[:alpha:][:digit:]-", 10) ))
upper : (( rand("A-Z", 10) ))
punct : (( rand("[:punct:]", 10) ))
alnum : (( rand("[:alnum:]", 10) ))
تقييم ل
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
يعطي type
الوظيفة سلسلة تشير إلى نوع التعبير المحدد.
على سبيل المثال:
template :
<< : (( &template ))
types :
- int : (( type(1) ))
- float : (( type(1.0) ))
- bool : (( type(true) ))
- string : (( type("foobar") ))
- list : (( type([]) ))
- map : (( type({}) ))
- lambda : (( type(|x|->x) ))
- template : (( type(.template) ))
- nil : (( type(~) ))
- undef : (( type(~~) ))
يقيم الأنواع ل
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
تحدد الوظيفة defined
ما إذا كان يمكن تقييم التعبير بنجاح. إنها تعطي قيمة منطقية true
، إذا كان يمكن تقييم التعبير ، false
خلاف ذلك.
على سبيل المثال:
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
تقييم ل
zero : 0
div_ok : false
zero_def : true
null_def : false
يمكن استخدام هذه الوظيفة جنبًا إلى جنب من المشغل الشرطي لتقييم التعبيرات اعتمادًا على قابلية حل تعبير آخر.
(( valid(foobar) ))
تتحقق الوظيفة valid
ما إذا كان يمكن تقييم التعبير بنجاح وتقييم قيمة محددة لا تساوي nil
. إنها تعطي قيمة منطقية true
، إذا كان يمكن تقييم التعبير ، false
خلاف ذلك.
على سبيل المثال:
zero : 0
empty :
map : {}
list : []
div_ok : (( valid(1 / zero ) ))
zero_def : (( valid( zero ) ))
null_def : (( valid( ~ ) ))
empty_def : (( valid( empty ) ))
map_def : (( valid( map ) ))
list_def : (( valid( list ) ))
تقييم ل
zero : 0
empty : null
map : {}
list : []
div_ok : false
zero_def : true
null_def : false
empty_def : false
map_def : true
list_def : true
(( require(foobar) ))
require
الوظيفة أن تعطي خطأ إذا كانت الوسيطة المحددة غير محددة أو nil
، وإلا فإنها تعطي القيمة المحددة.
على سبيل المثال:
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
تقييم ل
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
ينتج stub
الوظيفة قيمة حقل مخصص موجود في أول كوب من المنبع الذي يحدده.
على سبيل المثال:
قالب
value : (( stub(foo.bar) ))
اندمجت مع كعب
كوب
foo :
bar : foobar
تقييم ل
value : foobar
يجب أن تكون الوسيطة التي تم تمريرها إلى هذه الوظيفة إما حرفيًا مرجعيًا أو تعبيرًا يقييم إما سلسلة تشير إلى مرجع أو قائمة سلسلة تدل على قائمة عناصر المسار للمرجع. إذا لم يتم تقديم وسيطة أو غير محددة ( ~~
) ، يتم استخدام مسار الحقل الفعلي.
يرجى ملاحظة أنه لن يتم تقييم مرجع وحيد معين كتعبير ، إذا كان ينبغي استخدام قيمته ، فيجب تحويله إلى تعبير ، على سبيل المثال عن طريق الإشارة (ref)
أو [] ref
للتعبير عن القائمة.
بدلاً من ذلك ، يمكن استخدام عملية merge
، على سبيل المثال merge foo.bar
. الفرق هو أن stub
لا يندمج ، وبالتالي سيظل الحقل مدمرًا (مع المسار الأصلي في المستند).
(( tagdef("tag", value) ))
يمكن استخدام وظيفة tagdef
لتحديد العلامات الديناميكية (انظر العلامات). على عكس علامة العلامة ، تتيح هذه الوظيفة تحديد اسم العلامة والقيمة المقصودة من خلال التعبير. لذلك ، يمكن استخدامه في تأليف عناصر مثل map
أو sum
لإنشاء علامة ديناميكية مع قيم محسوبة.
يمكن استخدام وسيطة ثالثة اختيارية لتحديد النطاق المقصود ( local
أو global
). بشكل افتراضي ، يتم إنشاء علامة محلية. العلامات المحلية مرئية فقط على مستوى المعالجة الفعلي (القالب أو الفرعي) ، في حين يمكن استخدام العلامات العالمية ، بمجرد تحديدها ، في جميع مستويات المعالجة الإضافية (كعب أو قالب).
بدلاً من ذلك ، يمكن أن يكون اسم العلامة بادئة مع بداية ( *
) لإعلان علامة عالمية.
سيتم استخدام قيمة العلامة المحددة كنتيجة للوظيفة.
على سبيل المثال:
قالب
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
تقييم ل
value : 25
alice : 25
(( eval(foo "." bar ) ))
تقييم نتيجة التقييم لتعبير السلسلة مرة أخرى كتعبير ديناميكي. يمكن استخدام هذا ، على سبيل المثال ، لإدراك الاتجاهات.
على سبيل المثال: التعبير في
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
يحسب المسار إلى الحقل ، ثم يتم تقييمه مرة أخرى لإنتاج قيمة هذا الحقل المؤلف:
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
اقرأ قيمة متغير البيئة الذي يتم إعطاء اسمه كتعبير ديناميكي. إذا لم يتم تعيين متغير البيئة فشل التقييم.
في النكهة الثانية ، تقبل الوظيفة env
الوسائط و/أو القائمة المتعددة ، والتي يتم ضمها إلى قائمة واحدة. يتم استخدام كل إدخال في هذه القائمة كاسم لمتغير البيئة ونتيجة الوظيفة هي خريطة للمتغيرات المعطاة كعنصر yaml. بموجب هذا يتم حذف متغيرات البيئة غير الموجودة.
(( parse(yamlorjson) ))
تحليل سلسلة yaml أو json وإرجاع المحتوى كقيمة yaml. وبالتالي يمكن استخدامه لمزيد من تقييم الدينامير.
على سبيل المثال:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
تعطي القيمة 25
result
الحقل.
يدعم parse
الوظيفة وسيطة ثانية اختيارية ، وضع Parse . هنا تكون أوضاع نفسها ممكنة بالنسبة لوظيفة القراءة. وضع التحليل الافتراضي هو import
، والمحتوى محلل فقط وليس هناك تقييم آخر خلال هذه الخطوة.
(( asjson(expr) ))
تقوم هذه الوظيفة بتحويل قيمة yaml التي قدمتها وسيطة إلى سلسلة JSON . تعطي الوظيفة المقابلة asyaml
قيمة YAML كسلسلة مستند YAML .
على سبيل المثال:
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
يحل
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
تنفذ هذه الوظيفة تعبيرًا وتعطي بعض خريطة معلومات التقييم. ينجح دائمًا ، حتى لو فشل التعبير. تتضمن الخريطة الحقول التالية:
اسم | يكتب | معنى |
---|---|---|
valid | منطقي | التعبير صالح |
error | خيط | نص رسالة الخطأ للتقييم |
value | أي | قيمة التعبير ، إذا كان التقييم ناجحًا |
على سبيل المثال:
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
يحل
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
إنشاء قائمة من IPS ثابتة لوظيفة.
على سبيل المثال:
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
سيؤدي ذلك إلى إنشاء 3 IPS من الشبكة الفرعية mynetwork
، وإعادة إدخالين ، حيث لا يوجد سوى حالتان. سيكون الإدخالات هما التعويضات 0 والثالثة من نطاقات IP الثابت التي تحددها الشبكة.
على سبيل المثال ، بالنظر إلى الملف bye.yml :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
وملف hi.yml :
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
spiff merge bye.yml hi.yml
يعود
jobs :
- instances : 3
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
- 10.60.3.70
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
.
إذا كان وداعا بدلاً من ذلك
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
بدلا من ذلك يعود
jobs :
- instances : 2
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
يقبل static_ips
أيضًا وسيطات قائمة ، طالما أن جميع العناصر الموجودة على عابرة إما هي إما قوائم أو قيم عدد صحيح مرة أخرى. هذا يسمح بإختصار قائمة IPS على النحو التالي:
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
على الرغم من أن الوظيفة static_ips لأسباب تاريخية تعتمد على بنية بيان BOSH ولا تعمل إلا في مواقع مخصصة في البيان ، فإن وظيفة IPSET تقدم حسابًا مماثلًا يعتمد على وسيطاته. لذلك ، يتم تمرير نطاقات IP المتاحة والأرقام المطلوبة من IPs كوسائط.
يمكن أن تكون الوسيطة الأولى (النطاقات) نطاقًا واحدًا كسلسلة بسيطة أو قائمة من السلاسل. قد تكون كل سلسلة
تحدد الوسيطة الثانية العدد المطلوب لعناوين IP في مجموعة النتائج.
تحدد الوسائط الإضافية مؤشرات IPS للاختيار (بدءًا من 0) في النطاقات المحددة. هنا يمكن استخدام قوائم المؤشرات مرة أخرى.
على سبيل المثال:
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
يحل IPSET إلى [ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
.
إذا لم يتم تحديد مؤشرات IP (وسيطتين فقط) ، يتم اختيار IPs بدءًا من بداية النطاق الأول حتى نهاية النطاق الأخير ، دون عدم التوجيه.
(( list_to_map(list, "key") ))
سيتم تعيين قائمة بإدخالات الخريطة ذات الحقول/المفاتيح الصريحة على خريطة مع المفاتيح المخصصة. بشكل افتراضي ، يتم استخدام name
حقل المفتاح ، والذي يمكن أن يتغير بواسطة الوسيطة الثانية الاختيارية. كما سيتم أخذ حقل المفتاح الموجه صراحة في القائمة في الاعتبار.
على سبيل المثال:
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
سيتم تعيينه ل
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
بالاقتران مع القوالب وتعبيرات lambda ، يمكن استخدام هذا لإنشاء خرائط ذات قيم مفاتيح مسماة بشكل تعسفي ، على الرغم من أن تعبيرات Dynaml غير مسموح بقيم رئيسية.
(( makemap(fieldlist) ))
في هذه النكهة ، يقوم makemap
بإنشاء خريطة مع إدخالات وصفها قائمة الحقل المحددة. من المتوقع أن تحتوي القائمة على خرائط مع key
الإدخالات value
، ووصف إدخالات الخريطة المخصصة.
على سبيل المثال:
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
الغلة
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
إذا كانت قيمة المفتاح عبارة عن منطقية أو عدد صحيح ، فسيتم تعيينها إلى سلسلة.
(( makemap(key, value) ))
في هذه النكهة ، يقوم makemap
بإنشاء خريطة مع إدخالات وصفها أزواج الوسيطة المحددة. قد تكون الوسائط تسلسلًا من أزواج المفاتيح/القيم (المقدمة بواسطة وسيطات منفصلة).
على سبيل المثال:
map : (( makemap("peter", 23, "paul", 22) ))
الغلة
map :
paul : 22
peter : 23
على النقيض من نكهة makemap
السابقة ، يمكن أيضًا التعامل مع هذا النكهة من قبل الحرفيين MAP.
(( merge(map1, map2) ))
بجانب merge
الكلمة الرئيسية ، توجد أيضًا وظيفة تسمى merge
(يجب دائمًا أن تتبعها شريحة افتتاحية). يمكن استخدامه لدمج خرائط Severals المأخوذة من المستند الفعلي المماثل لعملية دمج كعب. إذا تم تحديد الخرائط عن طريق التعبيرات المرجعية ، فلن تتمكن من احتواء أي تعبيرات Dynaml ، لأنه يتم تقييمها دائمًا في سياق المستند الفعلي قبل تقييم الوسائط.
على سبيل المثال:
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
يحل result
ل
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
بدلاً من ذلك ، يمكن تمرير قوالب الخريطة (بدون مشغل تقييم!). في هذه الحالة ، يتم تقييم تعبيرات Dynaml من القالب أثناء دمج المستندات المحددة كما هو الحال بالنسبة للمكالمات العادية لدمج spiff .
على سبيل المثال:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
يحل result
ل
result :
alice : 26
bob : 26
قد يتم إعطاء الخريطة أيضًا عن طريق تعبير الخريطة. من الممكن هنا تحديد تعبيرات Dynaml باستخدام بناء الجملة المعتاد:
على سبيل المثال:
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
يحل result
ل
result :
alice : 26
bob : 100
بدلاً من الحجج المتعددة ، يمكن إعطاء وسيطة قائمة واحدة. يجب أن تحتوي القائمة على الخرائط المراد دمجها.
الدمج المتداخلة يمكن الوصول إلى جميع الروابط الخارجية. يتم تفتيش المراجع النسبية لأول مرة في المستند الفعلي. إذا لم يتم العثور عليها هناك يتم استخدام جميع الروابط الخارجية للبحث عن المرجع ، من الارتباطات الداخلية إلى الخارج. بالإضافة إلى ذلك ، يوفر السياق ( __ctx
) حقلًا OUTER
، وهو قائمة بجميع المستندات الخارجية للدمج المتداخلة ، والتي يمكن استخدامها للبحث عن المراجع المطلقة.
على سبيل المثال:
data :
alice :
age : 24
template :
<< : (( &template ))
bob : 25
outer1 : (( __ctx.OUTER.[0].data )) # absolute access to outer context
outer2 : (( data.alice.age )) # relative access to outer binding
sum : (( .bob + .outer2 ))
merged : (( merge(template) ))
تم merged
إلى
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
intersect
الوظيفة تتقاطع مع قوائم متعددة. قد تحتوي القائمة على إدخالات من أي نوع.
على سبيل المثال:
list1 :
- - a
- - b
- a
- b
- { a: b }
- { b: c }
- 0
- 1
- " 0 "
- " 1 "
list2 :
- - a
- - c
- a
- c
- { a: b }
- { b: b }
- 0
- 2
- " 0 "
- " 2 "
intersect : (( intersect(list1, list2) ))
يحل intersect
ل
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
reverse
الوظيفة يعكس ترتيب القائمة. قد تحتوي القائمة على إدخالات من أي نوع.
على سبيل المثال:
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
يحل reverse
ل
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
validate
الوظيفة يتحقق من صحة التعبير باستخدام مجموعة من المدققين. الوسيطة الأولى هي القيمة للتحقق من صحة وجميع الوسيطات الأخرى هي أخصائيات يجب أن تنجح في قبول القيمة. إذا فشل مدقق واحد على الأقل ، يتم إنشاء رسالة خطأ مناسبة تشرح سبب الفشل.
يتم الإشارة إلى المدقق بواسطة سلسلة أو قائمة تحتوي على نوع المدقق كسلسلة ووسائطه. يمكن أن ينفي المدقق مع مسبق !
باسمها.
المدققون التاليون متاحون:
يكتب | الحجج | معنى |
---|---|---|
empty | لا أحد | قائمة فارغة أو خريطة أو سلسلة |
dnsdomain | لا أحد | اسم مجال DNS |
wildcarddnsdomain | لا أحد | Wildcard DNS اسم مجال |
dnslabel | لا أحد | علامة DNS |
dnsname | لا أحد | مجال DNS أو مجال البطاقة البرية |
ip | لا أحد | عنوان IP |
cidr | لا أحد | CIDR |
publickey | لا أحد | المفتاح العام في تنسيق PEM |
privatekey | لا أحد | المفتاح الخاص في تنسيق PEM |
certificate | لا أحد | شهادة في تنسيق PEM |
ca | لا أحد | شهادة CA. |
semver | قائمة اختيارية للقيود | التحقق من صحة إصدار Semver مقابل القيود |
type | قائمة مفاتيح النوع المقبول | يجب أن يتطابق مفتاح نوع واحد على الأقل |
valueset | قائمة الوسيطة مع القيم | القيم الممكنة |
value أو = | قيمة | تحقق من قيمة مخصصة |
gt أو > | قيمة | أكبر من (الرقم/السلسلة) |
lt أو < | قيمة | أقل من (الرقم/السلسلة) |
ge أو >= | قيمة | أكبر أو تساوي (الرقم/السلسلة) |
le أو <= | قيمة | أقل أو يساوي (الرقم/السلسلة) |
match أو ~= | تعبير منتظم | قيمة السلسلة مطابقة التعبير العادي |
list | قائمة اختيارية لمقحة الدخول | هي قائمة وإدخالات مطابقة المُثدين |
map | [[<key valitator> ،] <Entry Valitator>] | هو خريطة ومفاتيح وإدخالات مطابقة المُقدين |
mapfield | <اسم الحقل> [، <erdalator>] | دخول مطلوب في الخريطة |
optionalfield | <اسم الحقل> [، <erdalator>] | دخول اختياري في الخريطة |
and | قائمة المدققين | يجب أن ينجح جميع المحققين |
or | قائمة المدققين | يجب أن ينجح مدقق واحد على الأقل |
not أو ! | المدقق | نفي حجة (ق) المدقق |
إذا نجح التحقق من الصحة يتم إرجاع القيمة.
على سبيل المثال:
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
تقييم ل
dnstarget : 192.168.42.42
إذا فشل التحقق من صحة خطأ في شرح سبب الفشل.
على سبيل المثال:
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
يعطي الخطأ التالي:
*condition 1 failed: (is no ip address: alice+bob and is no dns domain: [a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')])
قد يكون المدقق أيضًا عبارة عن تعبير Lambda يأخذ حجة واحدة على الأقل وإعادة قيمة منطقية. وبهذه الطريقة ، من الممكن تزويد المدققون الخاصين كجزء من وثيقة YAML.
على سبيل المثال:
val : (( validate( 0, |x|-> x > 1 ) ))
إذا تم الإعلان عن أكثر من معلمة واحدة ، فيجب تحديد الوسائط الإضافية كوسائط للتحقق. الوسيطة الأولى هي دائما القيمة للتحقق.
على سبيل المثال:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
قد تُرجع وظيفة Lambda قائمة بعناصر واحدة أو 2 أو 3 أيضًا. يمكن استخدام هذا لتوفير الرسائل المناسبة.
فِهرِس | معنى |
---|---|
0 | الفهرس الأول دائمًا هو نتيجة المطابقة ، يجب أن يكون قابلاً للتقييم على أنه منطقية |
1 | إذا تم تقديم عنصرين ، فإن الفهرس الثاني هو الرسالة التي تصف النتيجة الفعلية |
2 | هنا الفهرس 1 ينص على رسالة النجاح و 2 رسالة الفشل |
على سبيل المثال:
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
فقط على سبيل المثال ، قد يتم إعطاء مواصفات المدقق مضمونة كما هو موضح في الأمثلة أعلاه ، ولكن كتعبيرات مرجعية ، أيضًا. not
يقبل الموظفون غير المتداخلون and
/ or
المدققون مواصفات المدقق المتداخلة بعمق.
على سبيل المثال:
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
يمكن استخدام check
الوظيفة لمطابقة بنية YAML مقابل مدقق القيمة القائم على YAML. بموجب هذا ، يمكن استخدام وصف التحقق نفسه الموضح بالفعل للتحقق. نتيجة المكالمة هي قيمة منطقية تشير إلى نتيجة المطابقة. لا يفشل إذا فشل الشيك.
(( error("message") ))
يمكن استخدام error
الوظيفة للتسبب في فشل التقييم الصريح مع رسالة مخصصة.
يمكن استخدام ذلك ، على سبيل المثال ، لتقليل خطأ المعالجة المعقد إلى رسالة ذات معنى عن طريق إلحاق وظيفة الخطأ بأنها افتراضية للتعبير الذي يحتمل أن يفشل.
على سبيل المثال:
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
يمكن أن يكون سيناريو آخر هو حذف رسالة وصفية للمفقودين الحقول المطلوبة باستخدام تعبير خطأ كقيمة (افتراضي) لحقل مخصص ليتم تعريفه في كعب المنبع.
تدعم Dynaml وظائف الرياضيات المختلفة:
الأعداد الصحيحة العائدين: ceil
floor
round
roundtoeven
عوامات أو أعداد صحيحة: abs
عوامات العودة: sin
، cos
، sinh
، cosh
، asin
، acos
، asinh
، acosh
، sqrt
، exp
، log
، log10
،
يدعم Dynaml تحويلات النوع المختلفة بين قيم integer
، float
، bool
وقيم string
من خلال الوظائف المناسبة.
على سبيل المثال:
value : (( integer("5") ))
يحول سلسلة إلى قيمة عدد صحيح.
يقبل تحويل عدد صحيح إلى سلسلة وسيطة عدد صحيح إضافي اختياري لتحديد قاعدة التحويل ، على سبيل المثال ستؤدي string(55,2)
إلى "110111"
. القاعدة الافتراضية هي 10. يجب أن تكون القاعدة بين 2 و 36.
يدعم SPIFF الوصول إلى المحتوى خارج القالب والملفات الفرعية. من الممكن قراءة الملفات وتنفيذ الأوامر وخطوط الأنابيب. كل هذه الوظائف موجودة في نكهات.
sync
، والتي تهدف إلى مزامنة معالجة القالب مع حالة تخصيص (المقدمة بواسطة المحتوى الخارجي). هنا لن تكون عمليات التخزين المؤقت مفيدة ، لذلك هناك نكهة ثانية غير متوفرة. كل وظيفة متوفرة مع اللاحقة _uncached
(على سبيل المثال read_uncached()
) (( read("file.yml") ))
اقرأ ملفًا وأرجع المحتوى الخاص به. هناك دعم لثلاثة أنواع محتوى: ملفات yaml
والملفات text
والملفات binary
. ستؤدي القراءة في الوضع الثنائي إلى سلسلة متعددة الخطوط المشفرة BASE64.
.yaml
كانت لاحقة الملف هي .yml
.json
إذا كان يجب قراءة الملف text
، فيجب تحديد هذا النوع بشكل صريح. في جميع الحالات الأخرى ، يكون الافتراضي هو text
، وبالتالي فإن قراءة ملف ثنائي (على سبيل المثال أرشيف) يتطلب بشكل عاجل تحديد الوضع binary
.
يمكن استخدام المعلمة الثانية الاختيارية لتحديد نوع الإرجاع المطلوب بشكل صريح: yaml
أو text
. بالنسبة لمستندات YAML ، يتم دعم بعض الأنواع الإضافية: multiyaml
، template
، templates
، import
importmulti
.
سيتم تحليل وثيقة Yaml وإعادة الشجرة. يمكن الوصول إلى عناصر الشجرة عن طريق تعبيرات Dynaml العادية.
بالإضافة إلى ذلك ، قد يحتوي ملف YAML مرة أخرى على تعبيرات Dynaml. سيتم تقييم جميع تعبيرات Dynaml المشمولة في سياق تعبير القراءة. هذا يعني أن نفس الملف المدرج في أماكن مختلفة في مستند YAML قد يؤدي إلى أشجار فرعية مختلفة ، اعتمادًا على تعبيرات Dynaml المستخدمة.
إذا كان من الممكن قراءة YAML متعددة الوثيقة ، أيضا. إذا تم إعطاء نوع multiyaml
، يتم إرجاع عقدة قائمة مع عقد جذر مستند yaml.
يمكن أن تقرأ مستند YAML أو JSON أيضًا كقالب عن طريق تحديد template
النوع. هنا ستكون النتيجة قيمة قالب ، يمكن استخدامها مثل القوالب المضمنة العادية. إذا تم تحديد templates
، يتم تعيين حجة متعددة في قائمة القوالب.
إذا تم تعيين نوع القراءة على import
، فسيتم قراءة محتوى الملف كمستند YAML ويتم استخدام عقدة الجذر لاستبدال التعبير. لن يتم تقييم تعبيرات Dynaml المحتملة الواردة في المستند مع الربط الفعلي للتعبير مع استدعاء القراءة ، ولكن كما كان سيكون جزءًا من الملف الأصلي. لذلك لا يمكن استخدام هذا الوضع إلا إذا لم يكن هناك معالجة أخرى لنتيجة القراءة أو أن القيم التي يتم تسليمها غير مجهزة.
يمكن استخدام ذلك مع مرجع بالسلاسل (للامتثال (( read(...).selection ))
) لحذف جزء مخصص من المستند المستورد. بعد ذلك ، سيتم إجراء التقييم للجزء المحدد فقط. التعبيرات والمراجع في الأجزاء الأخرى لا يتم تقييمها وعلى الإطلاق ولا يمكن أن تؤدي إلى خطأ.
على سبيل المثال:
قالب
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
import.yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
لن يفشل ، لأنه لا يتم تقييم القسم second
أبدًا.
يجب أن يؤخذ هذا الوضع بحذر ، لأنه غالبًا ما يؤدي إلى نتائج غير متوقعة.
يمكن استخدام نوع القراءة importmulti
لاستيراد ملفات YAML متعددة الحواسة كقائمة من العقد.
سيتم إرجاع مستند نصي كسلسلة واحدة.
من الممكن قراءة المستندات الثنائية أيضًا. لا يمكن استخدام المحتوى كسلسلة (أو مستند YAML) ، مباشرة. لذلك يجب تحديد وضع القراءة binary
. يتم إرجاع المحتوى كقيمة سلسلة متعددة الخطوط المشفرة BASE64.
(( exec("command", arg1, arg2) ))
تنفيذ أمر. يمكن أن تكون الوسيطات أي تعبيرات Dynaml بما في ذلك التعبيرات المرجعية التي تم تقييمها على القوائم أو الخرائط. يتم تمرير القوائم أو الخرائط كوسائط واحدة تحتوي على وثيقة YAML مع الشظية المحددة.
يتم تحديد النتيجة عن طريق تحليل الإخراج القياسي للأمر. قد يكون مستند YAML أو سلسلة واحدة متعددة الخطوط أو عدد صحيح. يجب أن يبدأ مستند YAML ببادئة المستند ---
. إذا فشل الأمر ، يتم التعامل مع التعبير على أنه غير محدد.
على سبيل المثال
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
الغلة
arg :
- a
- b
list :
- a
- b
string : a
بدلاً من ذلك ، يمكن استدعاء exec
مع وسيطة قائمة واحدة تصف سطر الأوامر بالكامل.
سيتم تنفيذ نفس الأمر مرة واحدة ، فقط ، حتى لو تم استخدامه في تعبيرات متعددة.
(( pipe(data, "command", arg1, arg2) ))
تنفيذ أمر وتغذية المدخلات القياسية مع بيانات مخصصة. يجب أن تكون وسيطة الأمر سلسلة. يمكن أن تكون وسيطات الأمر هي أي تعبيرات Dynaml بما في ذلك التعبيرات المرجعية التي تم تقييمها على القوائم أو الخرائط. يتم تمرير القوائم أو الخرائط كوسائط واحدة تحتوي على وثيقة YAML مع الشظية المحددة.
يتم إنشاء دفق الإدخال من البيانات المحددة. إذا كان هذا نوعًا بسيطًا ، فسيتم استخدام تمثيل السلسلة الخاص به. وإلا يتم إنشاء مستند YAML من بيانات الإدخال. يتم تحديد النتيجة عن طريق تحليل الإخراج القياسي للأمر. قد يكون مستند YAML أو سلسلة واحدة متعددة الخطوط أو عدد صحيح. يجب أن يبدأ مستند YAML ببادئة المستند ---
. إذا فشل الأمر ، يتم التعامل مع التعبير على أنه غير محدد.
على سبيل المثال
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
الغلة
arg :
- a
- b
list :
- z
- b
بدلاً من ذلك ، يمكن استدعاء pipe
بالبيانات ووسيطة قائمة تصف سطر الأوامر بالكامل.
سيتم تنفيذ نفس الأمر مرة واحدة ، فقط ، حتى لو تم استخدامه في تعبيرات متعددة.
(( write("file.yml", data) ))
اكتب ملفًا وأرجع محتواه. إذا كان يمكن تحليل النتيجة كوثائق YAML ، يتم إرجاع المستند. يمكن استخدام الوسيطة الثالثة الاختيارية لتمرير خيارات الكتابة. قد تكون وسيطات الخيار عبارة عن عدد صحيح يدل على أذونات الملف (الافتراضي هو 0644
) أو سلسلة مفصولة بفاصلة مع خيارات. الخيارات المدعومة هي
binary
: البيانات base64 فك تشفيرها قبل الكتابة0
قيادة إلى قيمة ثماني. (( tempfile("file.yml", data) ))
اكتب ملف AA المؤقت وإرجاع اسم المسار. يمكن استخدام الوسيطة الثالثة الاختيارية لتمرير خيارات الكتابة. إنه يتصرف بشكل أساسي مثل write
الانتباه : يوجد ملف مؤقت فقط أثناء معالجة الدمج. سيتم حذفه بعد ذلك.
يمكن استخدامه ، على سبيل المثال ، توفير وسيطة ملف مؤقت لوظيفة exec
.
(( lookup_file("file.yml", list) ))
البحث عن ملف هو قائمة الدلائل. والنتيجة هي قائمة الملفات الموجودة. مع lookup_dir
من الممكن البحث عن دليل ، بدلاً من ذلك.
إذا لم يتم العثور على أي ملفات موجودة ، يتم إرجاع القائمة الفارغة.
من الممكن تمرير قوائم أو وسيطات متعددة لتكوين مسار البحث.
(( mkdir("dir", 0755) ))
قم بإنشاء دليل وجميع أدلةه الوسيطة إذا لم تكن موجودة بعد.
جزء الإذن اختياري (افتراضي 0755). قد يتم إعطاء مسار الدليل عن طريق القيمة مثل القيمة أو كقائمة من مكونات المسار.
(( list_files(".") ))
قائمة الملفات في الدليل. والنتيجة هي قائمة الملفات الموجودة. مع list_dirs
من الممكن سرد الدلائل ، بدلاً من ذلك.
(( archive(files, "tar") ))
قم بإنشاء أرشيف من النوع المحدد (الافتراضي هو tar
) الذي يحتوي على الملفات المدرجة. والنتيجة هي أرشيف Base64 المشفر.
أنواع الأرشيف المدعومة هي tar
و targz
.
قد تكون files
قائمة أو خريطة إدخالات الملفات. في حالة الخريطة ، يتم استخدام مفتاح الخريطة كإعداد افتراضي لمسار الملف. إدخال الملف هو خريطة مع الحقول التالية:
مجال | يكتب | معنى |
---|---|---|
path | خيط | اختياري للخرائط ، مسار الملف في الأرشيف ، تم التخرف به بواسطة مفتاح الخريطة |
mode | int أو int string | وضع الملف أو خيارات الكتابة. إنه يتصرف بشكل أساسي مثل وسيطة الخيار write . |
data | أي | محتوى الملف ، سيتم تنظيم YAML كوثيقة YAML. إذا كان mode يشير إلى الوضع الثنائي ، فسيتم فك تشفير قيمة السلسلة BASE64. |
base64 | خيط | البيانات الثنائية المشفرة BASE64 |
على سبيل المثال:
yaml :
alice : 26
bob : 27
files :
" data/a/test.yaml " :
data : (( yaml ))
" data/b/README.md " :
data : |+
### Test Docu
**Note**: This is a test
archive : (( archive(files,"targz") ))
content : (( split("n", exec_uncached("tar", "-tvf", tempfile(archive,"binary"))) ))
غلة:
archive : |-
H4sIAAAAAAAA/+zVsQqDMBAG4Mx5igO3gHqJSQS3go7tUHyBqIEKitDEoW9f
dLRDh6KlJd/yb8ll+HOd8SY1qbfOJw8zDmQHiIhayjURcZuIQhOeSZlphVwL
glwsAXvM8mJ23twJ4qfnbB/3I+I4pmboW1uA0LSZmgJETr89VXCUtf9Neq1O
5blKxm6PO972X/FN/7nKVej/EaIogto6D+XUzpQydpm8ZayA+tY76B0YWHYD
DV9CEATBf3kGAAD//5NlAmIADAAA
content :
- -rw-r--r-- 0/0 22 2019-03-18 09:01 data/a/test.yaml
- -rw-r--r-- 0/0 41 2019-03-18 09:01 data/b/README.md
files :
data/a/test.yaml :
data :
alice : 26
bob : 27
data/b/README.md :
data : |+
### Test Docu
**Note**: This is a test
yaml :
alice : 26
bob : 27
يدعم Spiff التعامل مع أسماء الإصدارات الدلالية. وهو يدعم جميع الوظائف من حزمة العقل المدبر لقبول الإصدارات مع أو بدون v
(( semver("v1.2-beta.1") ))
تحقق مما إذا كانت سلسلة معينة هي نسخة دلالية وإرجاع نموذجها الطبيعي (دون قيادة v
وإكمال جزء الإصدار مع رقم الإصدار الرئيسي والثاني والتصحيح).
على سبيل المثال:
normalized : (( semver("v1.2-beta.1") ))
يحل
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
إرجاع جزء الإصدار من نسخة دلالية تحذف معلومات البيانات الوصفية و PRERELEASE.
على سبيل المثال:
release : (( semverrelease("v1.2.3-beta.1") ))
يحل
release : v1.2.3
إذا تم إعطاء وسيطة سلسلة إضافية ، فهذه الوظيفة تحل محل الإصدار من خلال إصدار النسخة الدلالية المحددة التي تحافظ على معلومات البيانات الوصفية ومعلومات PRERELEASE.
على سبيل المثال:
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
يحل
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
تحديد رقم الإصدار الرئيسي للنسخة الدلالية المحددة. والنتيجة هي عدد صحيح.
على سبيل المثال:
major : (( semvermajor("1.2.3-beta.1") ))
يحل
major : 1
يمكن استخدام الوظيفة semverincmajor
لزيادة رقم الإصدار الرئيسي وإعادة ضبط الإصدار الثانوي ، وإصدار التصحيح واللواحق.
على سبيل المثال:
new : (( semverincmajor("1.2.3-beta.1") ))
يحل
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
تحديد رقم الإصدار الثانوي للنسخة الدلالية المحددة. والنتيجة هي عدد صحيح.
على سبيل المثال:
minor : (( semverminor("1.2.3-beta.1") ))
يحل
minor : 2
يمكن استخدام وظيفة semverincminor
لزيادة رقم الإصدار البسيط وإعادة ضبط إصدار التصحيح وإصدار اللواحق.
على سبيل المثال:
new : (( semverincmajor("v1.2.3-beta.1") ))
يحل
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
تحديد رقم إصدار التصحيح للنسخة الدلالية المحددة. والنتيجة هي عدد صحيح.
على سبيل المثال:
patch : (( semverpatch("1.2.3-beta.1") ))
يحل
patch : 3
يمكن استخدام وظيفة semverincpatch
لزيادة رقم إصدار التصحيح أو إعادة تعيين لاحقة الإصدار. إذا كانت هناك لاحقة Rlease ، فسيتم إزالتها وإبقاء معلومات الإصدار دون تغيير ، وإلا يتم زيادة رقم إصدار التصحيح.
على سبيل المثال:
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
يحل
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
تحديد ما قبل الإصدار الدلالي المعطى. والنتيجة هي سلسلة.
على سبيل المثال:
prerelease : (( semverprerelease("1.2.3-beta.1") ))
يحل
prerelease : beta.1
إذا تم إعطاء وسيطة سلسلة إضافية هذه الوظيفة ، فسيتم استبدالها أو مسحها (إذا تم تعيينها على سلسلة فارغة)
على سبيل المثال:
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
يحل
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
تحديد البيانات الوصفية للنسخة الدلالية المحددة. والنتيجة هي سلسلة.
على سبيل المثال:
metadata : (( semvermetadata("1.2.3+demo") ))
يحل
metadata : demo
إذا تم إعطاء وسيطة سلسلة إضافية هذه الوظيفة ، فسيتم استبدالها أو مسحها (إذا تم تعيينها على سلسلة فارغة) البيانات الوصفية.
على سبيل المثال:
new : (( semvermetadata("1.2.3-test", "demo) ))
يحل
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
مقارنة نسختين الدلاليين. Prelease هو دائما أصغر من الإصدار النهائي. والنتيجة هي عدد صحيح مع القيم التالية:
نتيجة | معنى |
---|---|
-1 | الإصدار الأول قبل الإصدار الثاني |
0 | كلا الإصدارين متساويان |
1 | أول Versuon هو بعد الثاني |
على سبيل المثال:
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
يحل
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
تطابق النسخة الدلالية المحددة مقابل قائمة من الموازين. والنتيجة هي منطقية. من الممكن تحديد أي عدد من قيود الإصدار. إذا لم يتم إعطاء أي قيد ، فإن الوظيفة تتحقق فقط مما إذا كانت السلسلة المحددة هي نسخة دلالية.
على سبيل المثال:
match : (( semvermatch("1.2.3", "~1.2") ))
يحل
match : true
يمكن العثور على القائمة الكاملة للقيود المحتملة هنا.
(( semversort("1.2.3", "1.2.1") ))
فرز قائمة الإصدارات في ترتيب تصاعدي. يتم الحفاظ على v
الرائدة.
على سبيل المثال:
sorted : (( semversort("1.2.3", "1.2.1") ))
يحل
sorted :
- 1.2.1
- 1.2.3
يمكن أيضًا تحديد قائمة الإصدارات المراد فرزها باستخدام وسيطة قائمة واحدة.
يدعم Spiff بعض الوظائف المفيدة للعمل مع شهادات ومفاتيح X509 . يرجى الرجوع أيضًا إلى القسم المفيد للتعرف على بعض النصائح لتوفير الحالة.
(( x509genkey(spec) ))
يمكن استخدام هذه الوظيفة إنشاء مفاتيح RSA أو ECDSA الخاصة. ستكون النتيجة مفتاحًا مشفرًا PEM كقيمة سلسلة خط متعددة. إذا تم إعطاء حجم المفتاح (عدد صحيح أو سلسلة) كوسيطة ، فسيتم إنشاء مفتاح RSA مع حجم المفتاح المحدد (على سبيل المثال 2048). إعطاء إحدى قيم السلسلة
ستولد الوظيفة مفتاح ECDSA المناسب.
على سبيل المثال:
keys :
key : (( x509genkey(2048) ))
يحل إلى شيء مثل
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
بالنسبة لمفتاح أو شهادة معينة بتنسيق PEM (على سبيل المثال ، تم إنشاؤها مع وظيفة X509GenKey) ، تستخرج هذه الوظيفة المفتاح العام وإرجاعه مرة أخرى بتنسيق PEM كسلسلة متعددة الخطوط.
على سبيل المثال:
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
يحل إلى شيء مثل
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
public : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rnsCxMx
vSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb325Bf/
...
VzYqyeQyvvRbNe73BXc5temCaQayzsbghkoWK+Wrc33yLsvpeVQBcB93Xhus+Lt1
1lxsoIrQf/HBsiu/5Q3M8L6klxeAUcDbYwIDAQAB
-----END RSA PUBLIC KEY-----
لإنشاء مفتاح Public SSH ، يمكن تعيين وسيطة تنسيق إضافية اختيارية على ssh
. ستكون النتيجة بعد ذلك تنسيق مفتاح عام منتظم قابل للاستخدام في SSH. التنسيق الافتراضي هو pem
توفير تنسيق إخراج PEM الموضح أعلاه.
مفاتيح RSA يتم بشكل افتراضي Marled في تنسيق PKCS#1 ( RSA PUBLIC KEY
) في PEM. إذا كان تنسيق PKIX العام ( PUBLIC KEY
) مطلوبًا ، فيجب إعطاء وسيطة التنسيق pkix
.
باستخدام التنسيق ssh
يمكن أيضًا استخدام هذه الوظيفة لتحويل مفتاح PEM المنسق إلى مفتاح SSH ،
(( x509cert(spec) ))
تقوم الدالة x509cert
بإنشاء شهادات موقعة محليًا ، إما إحدى الشهادات الموقعة ذاتيًا أو شهادة موقعة من قبل CA معين. يعيد شهادة مشفرة PEM كقيمة سلسلة متعددة الخطوط.
تأخذ المعلمة المواصفات الفردية خريطة مع بعض الحقول الاختيارية وغير الاختيارية المستخدمة لتحديد معلومات الشهادة. يمكن أن يكون تعبير خريطة مضمّن أو أي مرجع خريطة في بقية مستند YAML.
لوحظت حقول الخريطة التالية:
اسم الحقل | يكتب | مطلوب | معنى |
---|---|---|---|
commonName | خيط | خياري | حقل الاسم الشائع للموضوع |
organization | قائمة السلسلة أو السلسلة | خياري | مجال تنظيم الموضوع |
country | قائمة السلسلة أو السلسلة | خياري | مجال البلد للموضوع |
isCA | منطقي | خياري | خيار الشهادة CA |
usage | قائمة السلسلة أو السلسلة | مطلوب | مفاتيح الاستخدام للشهادة (انظر أدناه) |
validity | عدد صحيح | خياري | فاصل الصلاحية في ساعات |
validFrom | خيط | خياري | وقت البدء في التنسيق "1 يناير 01:22:31 2019" |
hosts | قائمة السلسلة أو السلسلة | خياري | قائمة أسماء DNS أو عناوين IP |
privateKey | خيط | مطلوب أو publickey | المفتاح الخاص لإنشاء شهادة |
publicKey | خيط | مطلوب أو خاص | المفتاح العام لإنشاء شهادة |
caCert | خيط | خياري | شهادة للتوقيع مع |
caPrivateKey | خيط | خياري | مفتاح Priavte لـ caCert |
بالنسبة للشهادات الموقعة ذاتيا ، يجب تعيين حقل privateKey
. يجب حذف publicKey
و ca
Fields. إذا تم إعطاء حقل caCert
، يلزم حقل caKey
أيضًا. إذا تم إعطاء حقل privateKey
مع caCert
، يتم استخراج المفتاح العام للشهادة من المفتاح الخاص.
يتم تجاهل الحقول الإضافية بصمت.
يتم دعم مفاتيح الاستخدام التالية (يتم تجاهل الحالة):
مفتاح | معنى |
---|---|
Signature | x509.keyusagediGitalsIntaUrate |
Commitment | x509.keyusageContEntCommitment |
KeyEncipherment | x509.keyusagekeyencipherment |
DataEncipherment | x509.keyusagedataencipherment |
KeyAgreement | x509.KeyusageKeyagreement |
CertSign | x509.keyusageCertSign |
CRLSign | x509.keyusagecrlsign |
EncipherOnly | x509.keyusageencipheronly |
DecipherOnly | x509.KeyusagedEcipheronly |
Any | x509.EXTEKEYUSAGEANY |
ServerAuth | x509.EXTEKEYUSAGESERVERAUTH |
ClientAuth | x509.EXTEKEYUSAGECLIENTAUTH |
codesigning | x509.EXTEKEYUSAGECODESIGNING |
EmailProtection | x509.ExteKeyUsageMailProtection |
IPSecEndSystem | x509.ExteKeyUsageSpecendsystem |
IPSecTunnel | x509.EXTEKEYUSAGEPSECTUNNEL |
IPSecUser | x509.EXTEKEYUSAGEIPSECUSER |
TimeStamping | x509.EXTEKEYUSAGETIMESTAMPING |
OCSPSigning | x509.ExteKeyusageOcSpsIscing |
MicrosoftServerGatedCrypto | x509.ExteKeyusagemicRosoftServergatedCrypto |
NetscapeServerGatedCrypto | x509.ExteKeyUsagenetsCapeservergatedCrypto |
MicrosoftCommercialCodeSigning | x509.ExteKeyUsagemicRosoftCommerCialCodesigning |
MicrosoftKernelCodeSigning | x509.ExteKeyusagemicRosoftKernelCodesigning |
على سبيل المثال:
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
يولد شهادة جذر موقعة ذاتيا ويحل إلى شيء مثل
cakey : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
cacert : |+
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIQb5ex4iGfyCcOa1RvnKSkMDANBgkqhkiG9w0BAQsFADAk
MQ8wDQYDVQQKEwZTQVAgU0UxETAPBgNVBAMTCGdhcmRlbmVyMB4XDTE4MTIzMTE0
...
pOUBE3Tgim5rnpa9K9RJ/m8IVqlupcONlxQmP3cCXm/lBEREjODPRNhU11DJwDdJ
5fd+t5SMEit2BvtTNFXLAwz48EKTxsDPdnHgiQKcbIV8NmgUNPHwXaqRMBLqssKl
Cyvds9xGtAtmZRvYNI0=
-----END CERTIFICATE-----
(( x509parsecert(cert) ))
تقوم هذه الوظيفة بتوصيف الشهادة الواردة في تنسيق PEM وإرجاع خريطة للحقول:
اسم الحقل | يكتب | مطلوب | معنى |
---|---|---|---|
commonName | خيط | خياري | حقل الاسم الشائع للموضوع |
organization | قائمة السلسلة | خياري | مجال تنظيم الموضوع |
country | قائمة السلسلة | خياري | مجال البلد للموضوع |
isCA | منطقي | دائماً | خيار الشهادة CA |
usage | قائمة السلسلة | دائماً | مفاتيح الاستخدام للشهادة (انظر أدناه) |
validity | عدد صحيح | دائماً | فاصل الصلاحية في ساعات |
validFrom | خيط | دائماً | وقت البدء في التنسيق "1 يناير 01:22:31 2019" |
validUntil | خيط | دائماً | وقت البدء في التنسيق "1 يناير 01:22:31 2019" |
hosts | قائمة السلسلة | خياري | قائمة أسماء DNS أو عناوين IP |
dnsNames | قائمة السلسلة | خياري | قائمة أسماء DNS |
ipAddresses | قائمة السلسلة | خياري | قائمة عناوين IP |
publicKey | خيط | دائماً | المفتاح العام لإنشاء شهادة |
على سبيل المثال:
data :
<< : (( &temporary ))
spec :
commonName : test
organization : org
validity : 100
isCA : true
privateKey : (( gen.key ))
hosts :
- localhost
- 127.0.0.1
usage :
- ServerAuth
- ClientAuth
- CertSign
gen :
key : (( x509genkey() ))
cert : (( x509cert(spec) ))
cert : (( x509parsecert(data.gen.cert) ))
يحل
cert :
commonName : test
dnsNames :
- localhost
hosts :
- 127.0.0.1
- localhost
ipAddresses :
- 127.0.0.1
isCA : true
organization :
- org
publickey : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+UIZQUTa/j+WlXC394bccBTltV+Ig3+zB1l4T6Vo36pMBmU4JIkJ
...
TCsrEC5ey0cCeFij2FijOJ5kmm4cK8jpkkb6fLeQhFEt1qf+QqgBw3targ3LnZQf
uE9t5MIR2X9ycCQSDNBxcuafHSwFrVuy7wIDAQAB
-----END RSA PUBLIC KEY-----
usage :
- CertSign
- ServerAuth
- ClientAuth
validFrom : Mar 11 15:34:36 2019
validUntil : Mar 15 19:34:36 2019
validity : 99 # yepp, that's right, there has already time passed since the creation
spiff supports some useful functions to work with wireguard keys. Please refer also to the Useful to Know section to find some tips for providing state.
(( wggenkey() ))
This function can be used generate private wireguard key. The result will base64 encoded.
على سبيل المثال:
keys :
key : (( wggenkey() ))
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
(( wgpublickey(key) ))
For a given key (for example generated with the wggenkey function) this function extracts the public key and returns it again in base64 format-
على سبيل المثال:
keys :
key : (( wggenkey() ))
public : (( wgpublickey(key)
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
public : n405KfwLpfByhU9pOu0A/ENwp0njcEmmQQJvfYHHQ2M=
(( lambda |x|->x ":" port ))
Lambda expressions can be used to define additional anonymous functions. They can be assigned to yaml nodes as values and referenced with path expressions to call the function with approriate arguments in other dynaml expressions. For the final document they are mapped to string values.
There are two forms of lambda expressions. بينما
lvalue : (( lambda |x|->x ":" port ))
yields a function taking one argument by directly taking the elements from the dynaml expression,
string : " |x|->x " : " port "
lvalue : (( lambda string ))
evaluates the result of an expression to a function. The expression must evaluate to a function or string. If the expression is evaluated to a string it parses the function from the string.
Since the evaluation result of a lambda expression is a regular value, it can also be passed as argument to function calls and merged as value along stub processing.
A complete example could look like this:
lvalue : (( lambda |x,y|->x + y ))
mod : (( lambda|x,y,m|->(lambda m)(x, y) + 3 ))
value : (( .mod(1,2, lvalue) ))
الغلة
lvalue : lambda |x,y|->x + y
mod : lambda|x,y,m|->(lambda m)(x, y) + 3
value : 6
If a complete expression is a lambda expression the keyword lambda
can be omitted.
Lambda expressions evaluate to lambda values, that are used as final values in yaml documents processed by spiff .
Note : If the final document still contains lambda values, they are transferred to a textual representation. It is not guaranteed that this representation can correctly be parsed again, if the document is re-processed by spiff . Especially for complex scoped and curried functions this is not possible.
Therefore function nodes should always be temporary or local to be available during processing or merging, but being omitted for the final document.
A typical function call uses positional arguments. Here the given arguments satisfy the declared function parameters in the given order. For lambda values it is also possible to use named arguments in the call expression. Here an argument is assigned to a dedicated parameter as declared by the lambda expression. The order of named arguments can be arbitrarily chosen.
على سبيل المثال:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, b=2, a=1) ))
It is also posible to combine named with positional arguments. Hereby the positional arguments must follow the named ones.
على سبيل المثال:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, 1, 2) ))
The same argument MUST NOT be satified by both, a named and a positional argument.
Instead of using the parameter name it is also possible to use the parameter index, instead.
على سبيل المثال:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(3=1, 1) ))
As such, this feature seems to be quite useless, but it shows its power if combined with optional parameters or currying as shown in the next paragraphs.
A lambda expression might refer to absolute or relative nodes of the actual yaml document of the call. Relative references are evaluated in the context of the function call. لذلك
lvalue : (( lambda |x,y|->x + y + offset ))
offset : 0
values :
offset : 3
value : (( .lvalue(1,2) ))
yields 6
for values.value
.
Besides the specified parameters, there is an implicit name ( _
), that can be used to refer to the function itself. It can be used to define self recursive function. Together with the logical and conditional operators a fibunacci function can be defined:
fibonacci : (( lambda |x|-> x <= 0 ? 0 :x == 1 ? 1 :_(x - 2) + _( x - 1 ) ))
value : (( .fibonacci(5) ))
yields the value 8
for the value
property.
By default reference expressions in a lambda expression are evaluated in the static scope of the lambda dedinition followed by the static yaml scope of the caller. Absolute references are always evalated in the document scope of the caller.
The name _
can also be used as an anchor to refer to the static definition scope of the lambda expression in the yaml document that was used to define the lambda function. Those references are always interpreted as relative references related to the this static yaml document scope. There is no denotation for accessing the root element of this definition scope.
Relative names can be used to access the static definition scope given inside the dynaml expression (outer scope literals and parameters of outer lambda parameters)
على سبيل المثال:
env :
func : (( |x|->[ x, scope, _, _.scope ] ))
scope : definition
call :
result : (( env.func("arg") ))
scope : call
yields the result
list:
call :
result :
- arg
- call
- (( lambda|x|->[x, scope, _, _.scope] )) # the lambda expression as lambda value
- definition
This also works across multiple stubs. The definition context is the stub the lambda expression is defined in, even if it is used in stubs down the chain. Therefore it is possible to use references in the lambda expression, not visible at the caller location, they carry the static yaml document scope of their definition with them.
Inner lambda expressions remember the local binding of outer lambda expressions. This can be used to return functions based on arguments of the outer function.
على سبيل المثال:
mult : (( lambda |x|-> lambda |y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
yields 6
for property value
.
Trailing parameters may be defaulted in the lambda expression by assigning values in the declaration. Those parameter are then optional, it is not required to specify arguments for those parameters in function calls.
على سبيل المثال:
mult : (( lambda |x,y=2|-> x * y ))
value : (( .mult(3) ))
yields 6
for property value
.
It is possible to default all parameters of a lambda expression. The function can then be called without arguments. There might be no non-defaulted parameters after a defaulted one.
A call with positional arguments may only omit arguments for optional parameters from right to left. If there should be an explicit argument for the right most parameter, arguments for all parameters must be specified or named arguments must be used. Here the desired optional parameter can explicitly be set prior to the regular positional arguments.
على سبيل المثال:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=3, 2) ))
evaluates result
to
result :
a : 2
b : 1
c : 3
The expression for the default does not need to be a constant value or even expression, it might refer to other nodes in the yaml document. The default expression is always evaluated in the scope of the lambda expression declaration at the time the lambda expression is evaluated.
على سبيل المثال:
stub.yaml
default : 2
mult : (( lambda |x,y=default * 2|-> x * y ))
template.yaml
mult : (( merge ))
scope :
default : 3
value : (( .mult(3) ))
evaluates value
to 12
The last parameter in the parameter list of a lambda expression may be a varargs parameter consuming additional argument in a fnction call. This parameter is always a list of values, one entry per additional argument.
A varargs parameter is denoted by a ...
following the last parameter name.
على سبيل المثال:
func : (( |a,b...|-> [a] b ))
result : (( .func(1,2,3) ))
yields the list [1, 2, 3]
for property result
.
If no argument is given for the varargs parameter its value is the empty list.
The ...
operator can also be used for inline list expansion.
If a vararg parameter should be set by a named argument its value must be a list.
Using the currying operator ( *(
) a lambda function may be transformed to another function with less parameters by specifying leading argument values.
The result is a new function taking the missing arguments (currying) and using the original function body with a static binding for the specified parameters.
على سبيل المثال:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult*(2) ))
value : (( .mult2(3) ))
Currying may be combined with defaulted parameters. But the resulting function does not default the leading parameters, it is just a new function with less parameters pinning the specified ones.
If the original function uses a variable argument list, the currying may span any number of the variable argument part, but once at least one such argument is given, the parameter for the variable part is satisfied. It cannot be extended by a function call of the curried function.
على سبيل المثال:
func : (( |a,b...|->join(a,b) ))
func1 : (( .func*(",","a","b")))
# invalid: (( .func1("c") ))
value : (( .func1() ))
evaluates value
to "a,b"
.
It is also possible to use currying for builtin functions, like join
.
على سبيل المثال:
stringlist : (( join*(",") ))
value : (( .stringlist("a", "b") ))
evaluates value
to "a,b"
.
There are several builtin functions acting on unevaluated or unevaluatable arguments, like defined
. For these functions currying is not possible.
Using positional arguments currying is only possible from right to left. But currying can also be done for named arguments. Here any parameter combination, regardless of the position in the parameter list, can be preset. The resulting function then has the unsatisfied parameters in their original order. Switching the parameter order is not possible.
على سبيل المثال:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry : (( .func(c=3, 2) ))
result : (( .curry(5) ))
evalutes result
to
result :
a : 2
b : 5
c : 3
The resulting function keeps the parameter b
. Hereby the default value will be kept. Therefore it can just be called without argument ( .curry()
), which would produce
result :
a : 2
b : 1
c : 3
انتباه :
For compatibility reasons currying is also done, if a lambda function without defaulted parameters is called with less arguments than declared parameters.
This behaviour is deprecated and will be removed in the future. It is replaced by the currying operator.
على سبيل المثال:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
evaluates value
to 6.
(( catch[expr|v,e|->v] ))
This expression evaluates an expression ( expr
) and then executes a lambda function with the evaluation state of the expression. It always succeeds, even if the expression fails. The lambda function may take one or two arguments, the first is always the evaluated value (or nil
in case of an error). The optional second argument gets the error message the evaluation of the expression failed (or nil
otherwise)
The result of the function is the result of the whole expression. If the function fails, the complete expression fails.
على سبيل المثال:
data :
fail : (( catch[1 / 0|v,e|->{$value=v, $error=e}] ))
valid : (( catch[5 * 5|v,e|->{$value=v, $error=e}] ))
resolves to
data :
fail :
error : division by zero
value : null
valid :
error : null
value : 25
(( sync[expr|v,e|->defined(v.field),v.field|10] ))
If an expression expr
may return different results for different evaluations, it is possible to synchronize the final output with a dedicated condition on the expression value. Such an expression could, for example, be an uncached read
, exec
or pipe
call.
The second element must evaluate to a lambda value, given by either a regular expression or by a lambda literal as shown in the title. It may take one or two arguments, the actual value of the value expression and optionally an error message in case of a failing evaluation. The result of the evaluation of the lamda expression decides whether the state of the evaluation of the value expression is acceptable ( true
) or not ( false
).
If the value is accepted, an optional third expression is used to determine the final result of the sync[]
expression. It might be given as an expression evaluating to a lambda value, or by a comma separated expression using the same binding as the preceeding lambda literal. If not given, the value of the synched expression is returned.
If the value is not acceptable, the evaluation is repeated until a timeout applies. The timeout in seconds is given by an optional fourth expression (default is 5 min). Either the fourth, or the both, the third and the fourth elements may be omitted.
The lambda values might be given as literal, or by expression, leading to the following flavors:
sync[expr|v,e|->cond,value|10]
sync[expr|v,e|->cond|valuelambda|10]
sync[expr|v,e|->cond|v|->value|10]
sync[expr|condlambda|valuelambda|10]
sync[expr|condlambda|v|->value|10]
with or without the timeout expression.
على سبيل المثال:
data :
alice : 25
result : (( sync[data|v|->defined(v.alice),v.alice] ))
resolves to
data :
alice : 25
result : 25
This example is quite useless, because the sync expression is a constant. It just demonstrates the usage.
Mappings are used to produce a new list from the entries of a list or map , or a new map from entries of a map containing the entries processed by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the mapping function: It can be inlined as in (( map[list|x|->x ":" port] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( map[list|mapping.expression))
(here the mapping is taken from the property mapping.expression
, which should hold an approriate lambda function).
The mapping comes in two target flavors: with []
or {}
in the syntax. The first flavor always produces a list from the entries of the given source. The second one takes only a map source and produces a filtered or transformed map .
Additionally the mapping uses three basic mapping behaviours:
map
. Here the result of the lambda function is used as new value to replace the original one. أوselect
. Here the result of the lambda function is used as a boolean to decide whether the entry should be kept ( true
) or omitted ( false
).sum
. Here always the list flavor is used, but the result type and content is completely determined by the parameterization of the statement by successively aggregating one entry after the other into an arbitrary initial value. Note : The special reference _
is not set for inlined lambda functions as part of the mapping syntax. Therefore the mapping statements (and all other statements using inlined lambda functions as part of their syntax) can be used inside regular lambda functions without hampering the meaning of this special refrence for the surrounding explicit lambda expression.
(( map[list|elem|->dynaml-expr] ))
Execute a mapping expression on members of a list to produce a new (mapped) list. The first expression ( list
) must resolve to a list. The last expression ( x ":" port
) defines the mapping expression used to map all members of the given list. Inside this expression an arbitrarily declared simple reference name (here x
) can be used to access the actually processed list element.
على سبيل المثال
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
الغلة
port : 4711
hosts :
- alice
- bob
mapped :
- alice:4711
- bob:4711
This expression can be combined with others, for example:
port : 4711
list :
- alice
- bob
joined : (( join( ", ", map[list|x|->x ":" port] ) ))
which magically provides a comma separated list of ported hosts:
port : 4711
list :
- alice
- bob
joined : alice:4711, bob:4711
(( map[list|idx,elem|->dynaml-expr] ))
In this variant, the first argument idx
is provided with the index and the second elem
with the value for the index.
على سبيل المثال
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
الغلة
list :
- name : alice
age : 25
- name : bob
age : 24
ages :
- 1. alice is 25
- 2. bob is 24
(( map[map|key,value|->dynaml-expr] ))
Mapping of a map to a list using a mapping expression. The expression may have access to the key and/or the value. If two references are declared, both values are passed to the expression, the first one is provided with the key and the second one with the value for the key. If one reference is declared, only the value is provided.
على سبيل المثال
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
الغلة
ages :
alice : 25
bob : 24
keys :
- alice
- bob
(( map{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys and the new entry values. As for a list mapping additionally a key variable can be specified in the variable list.
persons :
alice : 27
bob : 26
older : (( map{persons|x|->x + 1} ) ))
just increments the value of all entries by one in the field older
:
older :
alice : 28
bob : 27
ملاحظة
An alternate way to express the same is to use sum[persons|{}|s,k,v|->s { k = v + 1 }]
.
(( map{list|elem|->dynaml-expr} ))
Using {}
instead of []
together with a list in the mapping syntax, the result is again a map with the list elements as key and the mapped entry values. For this all list entries must be strings. As for a list mapping additionally an index variable can be specified in the variable list.
persons :
- alice
- bob
length : (( map{persons|x|->length(x)} ) ))
just creates a map mapping the list entries to their length:
length :
alice : 5
bob : 3
(( select[expr|elem|->dynaml-expr] ))
With select
a map or list can be filtered by evaluating a boolean expression for every entry. An entry is selected if the expression evaluates to true equivalent value. (see conditions).
Basically it offers all the mapping flavors available for map[]
على سبيل المثال
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
ملاحظة
An alternate way to express the same is to use map[list|v|->v.age > 25 ? v :~]
.
(( select{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys filtered by the given expression.
persons :
alice : 25
bob : 26
older : (( select{persons|x|->x > 25} ))
just keeps all entries with a value greater than 25 and omits all others:
selected :
bob : 26
This flavor only works on maps .
ملاحظة
An alternate way to express the same is to use sum[persons|{}|s,k,v|->v > 25 ? s {k = v} :s]
.
Aggregations are used to produce a single result from the entries of a list or map aggregating the entries by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the aggregation function: It can be inlined as in (( sum[list|0|s,x|->s + x] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( sum[list|0|aggregation.expression))
(here the aggregation function is taken from the property aggregation.expression
, which should hold an approriate lambda function).
(( sum[list|initial|sum,elem|->dynaml-expr] ))
Execute an aggregation expression on members of a list to produce an aggregation result. The first expression ( list
) must resolve to a list. The second expression is used as initial value for the aggregation. The last expression ( s + x
) defines the aggregation expression used to aggregate all members of the given list. Inside this expression an arbitrarily declared simple reference name (here s
) can be used to access the intermediate aggregation result and a second reference name (here x
) can be used to access the actually processed list element.
على سبيل المثال
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
الغلة
list :
- 1
- 2
sum : 3
(( sum[list|initial|sum,idx,elem|->dynaml-expr] ))
In this variant, the second argument idx
is provided with the index and the third elem
with the value for the index.
على سبيل المثال
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
الغلة
list :
- 1
- 2
- 3
prod : 8
(( sum[map|initial|sum,key,value|->dynaml-expr] ))
Aggregation of the elements of a map to a single result using an aggregation expression. The expression may have access to the key and/or the value. The first argument is always the intermediate aggregation result. If three references are declared, both values are passed to the expression, the second one is provided with the key and the third one with the value for the key. If two references are declared, only the second one is provided with the value of the map entry.
على سبيل المثال
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
الغلة
ages :
alice : 25
bob : 24
sum : 49
Projections work over the elements of a list or map yielding a result list. Hereby every element is mapped by an optional subsequent reference expression. This may contain again projections, dynamic references or lambda calls. Basically this is a simplified form of the more general mapping yielding a list working with a lambda function using only a reference expression based on the elements.
(( expr.[*].value ))
All elements of a map or list given by the expression expr
are dereferenced with the subsequent reference expression (here .expr
). If this expression works on a map the elements are ordered accoring to their key values. If the subsequent reference expression is omitted, the complete value list isreturned. For a list expression this means the identity operation.
على سبيل المثال:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[*].name ))
yields for names
:
names :
- alice
- bob
- peter
or for maps:
networks :
ext :
cidr : 10.8.0.0/16
zone1 :
cidr : 10.9.0.0/16
cidrs : (( .networks.[*].cidr ))
yields for cidrs
:
cidrs :
- 10.8.0.0/16
- 10.9.0.0/16
(( list.[1..2].value ))
This projection flavor only works for lists. The projection is done for a dedicated slice of the initial list.
على سبيل المثال:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[1..2].name ))
yields for names
:
names :
- bob
- peter
In argument lists or list literals the list expansion operator ( ...
) can be used. It is a postfix operator on any list expression. It substituted the list expression by a sequence of the list members. It can be be used in combination with static list argument denotation.
على سبيل المثال:
list :
- a
- b
result : (( [ 1, list..., 2, list... ] ))
evaluates result
to
result :
- 1
- a
- b
- 2
- a
- b
The following example demonstrates the usage in combination with the varargs operator in functions:
func : (( |a,b...|-> [a] b ))
list :
- a
- b
a : (( .func(1,2,3) ))
b : (( .func("x",list..., "z") ))
c : (( [ "x", .func(list...)..., "z" ] ))
evaluates the following results:
a :
- 1
- 2
- 3
b :
- x
- a
- b
- z
c :
- x
- a
- b
- z
Please note, that the list expansion might span multiple arguments (including the varargs parameter) in lambda function calls.
Nodes of the yaml document can be marked to enable dedicated behaviours for this node. Such markers are part of the dynaml syntax and may be prepended to any dynaml expression. They are denoted by the &
character directly followed by a marker name. If the expression is a combination of markers and regular expressions, the expression follows the marker list enclosed in brackets (for example (( &temporary( a + b ) ))
).
Note : Instead of using a <<:
insert field to place markers it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( &temporary ))
Maps, lists or simple value nodes can be marked as temporary . Temporary nodes are removed from the final output document, but are available during merging and dynaml evaluation.
على سبيل المثال:
temp :
<< : (( &temporary ))
foo : bar
value : (( temp.foo ))
yields:
value : bar
Adding - <<: (( &temporary ))
to a list can be used to mark a list as temporary.
The temporary marker can be combined with regular dynaml expressions to tag plain fields. Hereby the parenthesised expression is just appended to the marker
على سبيل المثال:
data :
alice : (( &temporary ( "bar" ) ))
foo : (( alice ))
yields:
data :
foo : bar
The temporary marker can be combined with the template marker to omit templates from the final output.
(( &local ))
The marker &local
acts similar to &temporary
but local nodes are always removed from a stub directly after resolving dynaml expressions. Such nodes are therefore not available for merging and they are not used for further merging of stubs and finally the template.
(( &dynamic ))
This marker can be used to mark a template expression (direct or referenced) to enforce the re-evaluation of the template in the usage context whenever the node is used to override or inject a node value along the processing chain. It can also be used together with &inject
or &default
.
على سبيل المثال:
template.yaml
data : 1
merged with
stub.yaml
id : (( &dynamic &inject &template(__ctx.FILE) ))
will resolve to
id : template.yaml
data : 1
The original template is kept along the merge chain and is evaluated separately in the context of the very stub or template it is used.
Using this marker for nodes not evaluationg to a template value is not possible.
(( &inject ))
This marker requests the marked item to be injected into the next stub level, even is the hosting element (list or map) does not requests a merge. This only works if the next level stub already contains the hosting element.
على سبيل المثال:
template.yaml
alice :
foo : 1
stub.yaml
alice :
bar : (( &inject(2) ))
nope : not injected
bob :
<< : (( &inject ))
foobar : yep
is merged to
alice :
foo : 1
bar : 2
bob :
foobar : yep
(( &default ))
Nodes marked as default will be used as default values for downstream stub levels. If no such entry is set there it will behave like &inject
and implicitly add this node, but existing settings will not be overwritten.
Maps (or lists) marked as default will be considered as values. The map is used as a whole as default if no such field is defined downstream.
على سبيل المثال:
template.yaml
data : { }
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
is merged to
data :
foobar :
foo : claude
bar : peter
Their entries will neither be used for overwriting existing downstream values nor for defaulting non-existng fields of a not defaulted map field.
على سبيل المثال:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
is merged to
data :
foobar :
bar : bob
If sub sequent defaulting is desired, the fields of a default map must again be marked as default.
على سبيل المثال:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : (( &default ("claude") ))
bar : peter
is merged to
data :
foobar :
foo : claude
bar : bob
Note : The behaviour of list entries marked as default is undefined.
(( &state ))
Nodes marked as state are handled during the merge processing as if the marker would not be present. But there will be a special handling for enabled state processing (option --state <path>
) at the end of the template processing. Additionally to the regular output a document consisting only of state nodes (plus all nested nodes) will be written to a state file. This file will be used as top-level stub for further merge processings with enabled state support.
This enables to keep state between two merge processings. For regular merging sich nodes are only processed during the first processing. Later processings will keep the state from the first one, because those nodes will be overiden by the state stub added to the end of the sub list.
If those nodes additionally disable merging (for example using (( &state(merge none) ))
) dynaml expressions in sub level nodes may perform explicit merging using the function stub()
to refer to values provided by already processed stubs (especially the implicitly added state stub). For an example please refer to the state library.
(( &template ))
Nodes marked as template will not be evaluated at the place of their occurrence. Instead, they will result in a template value stored as value for the node. They can later be instantiated inside a dynaml expression (see below).
(( &tag:name ))
The tag marker can be used to assign a logical name to a node value. This name can then be used in tagged reference expressions to refer to this node value (see below).
A tagged reference has the form <tagname>::<path>
. The <path>
may denote any sub node of a tagged node. If the value of a complete node (or a simple value node) should be used, the <path>
must denote the root path ( .
).
Tags can be used to label node values in multi-document streams (used as template). After defined for a document the tag can then be used to reference node values from the actual or previous document(s) of a document sequence in a multi-document stream. Tags can be added for complex or simple value nodes. A tagged reference may be used to refer to the tagged value as a whole or sub structure.
(( &tag:name(value) ))
This syntax is used to tag a node whose value is defined by a dynaml expression. It can also be used to denote tagged simple value nodes. (As usual the value part is optional for adding markers to structured values (see Markers).)
على سبيل المثال:
template.yaml
data :
<< : (( &tag:persons ))
alice : (( &tag:alice(25)
If the name is prefixed with a star ( *
), the tag is defined globally. Gobal tags surive stub processing and their value is visible in subsequent stub (and template) processings.
A tag name may consist of multiple components separated by a colon ( :
).
Tags can also be defined dynamically by the dynaml function tagdef.
(( tag::foo ))
Reference a sub path of the value of a tagged node.
على سبيل المثال:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
على سبيل المثال:
template.yaml
data :
alice : (( &tag:alice(25) ))
tagref : (( alice::. ))
resolves tagref
to 25
(( foo.bar::alice ))
Tag names may be structured. A tag name consists of a non-empty list of tag components separated by a dot or colon ( :
). A tag component may contain ASCII letters or numbers, starting wit a letter. Multi-component tags are subject to Tag Resolution.
A tag reference always contains a tag name and a path separated by a double colon ( ::
). The standard use-case is to describe a dedicated sub node for a tagged node value.
for example, if the tag X
describes the value
data :
alice : 25
bob : 24
the tagged reference X::data.alice
describes the value 25
.
For tagged references with a path other than .
(the whole tag value), structured tags feature a more sophisticated resolution mechanism. A structured tag consist of multiple tag components separated by a colon ( :
), for example lib:mylib
. Therefore, tags span a tree of namespaces or scopes used to resolve path references. A tag-less reference just uses the actual document or binding to resolve a path expression.
Evaluation of a path reference for a tag tries to resolve the path in the first tag tree level where the path is available (breadth-first search). If this level contains multiple tags that could resolve the given path, the resolution fails because it cannot be unambigiously resolved.
على سبيل المثال:
tags :
- << : (( &tag:lib:alice ))
data : alice.alice
- << : (( &tag:lib:alice:v1))
data : alice.v1
- << : (( &tag:lib:bob))
other : bob
usage :
data : (( lib::data ))
effectively resolves usage.data
to lib:alice::data
and therefore to the value alice.alice
.
To achieve this all matching sub tags are orderd by their number of tag components. The first sub-level tag containing such a given path is selected. For this level, the matching tag must be non-ambigious. There must only be one tag with this level containing a matching path. If there are multiple ones the evaluation fails. In the above example this would be the case if tag lib:bob
would contain a field data
instead of or additional to other
.
This feature can be used in library stubs to provide qualified names for their elements that can be used with merging the containing document nodes into the template.
If the template file is a multi-document stream the tags are preserved during the complete processing. This means tags defined in a earlier document can be used in all following documents, also. But the tag names must be unique across all documents in a multi-document stream.
على سبيل المثال:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
---
bob : (( persons::bob ))
resolves to
---
alice : 25
---
bob : 24
Tags defined by tag markers are available for stubs and templates. Global tags are available down the stub processing to the templates. Local tags are only avaialble on the processing level they are declared.
Additionally to the tags explicitly set by tag markers, there are implicit document tags given by the document index during the processing of a (multi-document) template. The implicit document tags are qualified with the prefix doc.
. This prefix should not be used to own tags in the documents
على سبيل المثال:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
prev : (( doc.1::. ))
---
bob : (( persons::bob ))
prev : (( doc.2::. ))
resolves to
---
alice : 25
prev :
data :
alice : 25
bob : 24
---
bob : 24
prev :
alice : 25
prev :
data :
alice : 25
bob : 24
If the given document index is negative it denotes the document relative to the one actually processed (so, the tag doc.-1
denotes the previous document). The index doc.0
can be used to denote the actual document. Here always a path must be specified, it is not possible to refer to the complete document (with .
).
A map can be tagged by a dynaml expression to be used as template. Dynaml expressions in a template are not evaluated at its definition location in the document, but can be inserted at other locations using dynaml. At every usage location it is evaluated separately.
<<: (( &template ))
The dynaml expression &template
can be used to tag a map node as template:
على سبيل المثال:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
The template will be the value of the node foo.bar
. As such it can be overwritten as a whole by settings in a stub during the merge process. Dynaml expressions in the template are not evaluated. A map can have only a single <<
field. Therefore it is possible to combine the template marker with an expression just by adding the expression in parenthesis.
Adding - <<: (( &template ))
to a list it is also possible to define list templates. It is also possible to convert a single expression value into a simple template by adding the template marker to the expression, for example foo: (( &template (expression) ))
The template marker can be combined with the temporary marker to omit templates from the final output.
Note : Instead of using a <<:
insert field to place the template marker it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( *foo.bar ))
The dynaml expression *<reference expression>
can be used to evaluate a template somewhere in the yaml document. Dynaml expressions in the template are evaluated in the context of this expression.
على سبيل المثال:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst : (( *foo.bar ))
verb : loves
verb : hates
evaluates to
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst :
alice : alice
bob : loves alice
verb : loves
verb : hates
_
The special reference _
( self ) can be used inside of lambda functions and templates . They refer to the containing element (the lambda function or template).
Additionally it can be used to lookup relative reference expressions starting with the defining document scope of the element skipping intermediate scopes.
على سبيل المثال:
node :
data :
scope : data
funcs :
a : (( |x|->scope ))
b : (( |x|->_.scope ))
c : (( |x|->_.data.scope ))
scope : funcs
call :
scope : call
a : (( node.funcs.a(1) ))
b : (( node.funcs.b(1) ))
c : (( node.funcs.c(1) ))
evaluates call
to
call :
a : call
b : funcs
c : data
scope : call
__
The special reference __
can be used to lookup references as relative references starting with the document node hosting the actually evaluated dynaml expression skipping intermediate scopes.
This can, for example be used to relatively access a lambda value field besides the actual field in a map. The usage of plain function names is reserved for builtin functions and are not used as relative references.
This special reference is also available in expressions in templates and refer to the map node in the template hosting the actually evaluated expression.
على سبيل المثال:
templates :
templ :
<< : (( &template ))
self : (( _ ))
value : (( ($self="value") __.self ))
result : (( scope ))
templ : (( _.scope ))
scope : templates
result :
inst : (( *templates.templ ))
scope : result
evaluates result
to
result :
inst :
result : result
templ : templates
self :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
value :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
scope : result
or with referencing upper nodes:
templates :
templ :
<< : (( &template ))
alice : root
data :
foo : (( ($bob="local") __.bob ))
bar : (( ($alice="local") __.alice ))
bob : static
result : (( *templates.templ ))
evaluates result
to
result :
alice : root
data :
bar : root
foo : static
bob : static
___
The special reference ___
can be used to lookup references in the outer most scope. It can therefore be used to access processing bindings specified for a document processing via command line or API. If no bindings are specified the document root is used.
Calling spiff merge template.yaml --bindings bindings.yaml
with a binding of
bindings.yaml
input1 : binding1
input2 : binding2
and the template
template.yaml
input1 : top1
map :
input : map
input1 : map1
results :
frommap : (( input1 ))
fromroot : (( .input1 ))
frombinding1 : (( ___.input1 ))
frombinding2 : (( input2 ))
evaluates map.results
to
results :
frombinding1 : binding1
frombinding2 : binding2
frommap : map1
fromroot : top1
__ctx.OUTER
The context field OUTER
is used for nested merges. It is a list of documents, index 0 is the next outer document, and so on.
(( {} ))
Provides an empty map.
(( [] ))
Provides an empty list. Basically this is not a dedicated literal, but just a regular list expression without a value.
(( ~ ))
Provides the null value.
(( ~~ ))
This literal evaluates to an undefined expression. The element (list entry or map field) carrying this value, although defined, will be removed from the document and handled as undefined for further merges and the evaluation of referential expressions.
على سبيل المثال:
foo : (( ~~ ))
bob : (( foo || ~~ ))
alice : (( bob || "default"))
evaluates to
alice : default
Inside every dynaml expression a virtual field __ctx
is available. It allows access to information about the actual evaluation context. It can be accessed by a relative reference expression.
The following fields are supported:
اسم الحقل | يكتب | معنى |
---|---|---|
VERSION | خيط | current version of spiff |
FILE | خيط | name of actually processed template file |
DIR | خيط | name of directory of actually processed template file |
RESOLVED_FILE | خيط | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | خيط | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | خيط | path name of actually processed field |
PATH | list[string] | path name as component list |
OUTER | yaml doc | outer documents for nested merges, index 0 is the next outer document |
BINDINGS | yaml doc | the external bindings for the actual processing (see also ___) |
If external bindings are specified they are the last elements in OUTER
.
على سبيل المثال:
template.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
على سبيل المثال:
foo :
bar :
dir : .
file : template.yml
path :
- foo
- bar
- path
str : foo.bar.str
Dynaml expressions are evaluated obeying certain priority levels. This means operations with a higher priority are evaluated first. For example the expression 1 + 2 * 3
is evaluated in the order 1 + ( 2 * 3 )
. Operations with the same priority are evaluated from left to right (in contrast to version 1.0.7). This means the expression 6 - 3 - 2
is evaluated as ( 6 - 3 ) - 2
.
The following levels are supported (from low priority to high priority)
||
, //
foo bar
)-or
, -and
==
, !=
, <=
, <
, >
, >=
+
, -
*
, /
, %
( )
, !
, constants, references ( foo.bar
), merge
, auto
, lambda
, map[]
, and functionsThe complete grammar can be found in dynaml.peg.
Feature state: alpha
Attention: This is an alpha feature. It must be enabled on the command line with the --interpolation
or --features=interpolation
option. Also for the spiff library it must explicitly be enabled. By adding the key interpolation
to the feature list stored in the environment variable SPIFF_FEATURES
this feature will be enabled by default.
Typically a complete value can either be a literal or a dynaml expression. For string literals it is possible to use an interpolation syntax to embed dynaml expressions into strings.
على سبيل المثال
data : test
interpolation : this is a (( data ))
replaces the part between the double brackets by the result of the described expression evaluation. Here the brackets can be escaped by the usual escaping ( ((!
) syntax.
Those string literals will implicitly be converted to complete flat dynaml expressions. The example above will therefore be converted into
(( "this is a " data ))
which is the regular dynaml equivalent. The escaping is very ticky, and may be there are still problems. Quotes inside an embedded dynaml expression can be escaped to enable quotes in string literals.
Incomplete or partial interpolation expressions will be ignored and just used as string.
Strings inside a dynaml expression are NOT directly interpolated again, thus
data : " test "
interpolated : " this is a (( length( " (( data )) " ) data )) "
will resolve interpolation
to this is 10test
and not to this is 4test
.
But if the final string after the expression evaluation again describes a string interpolation it will be processed, again.
data : test
interpolation : this is a (( "(( data ))" data ))
will resolve interpolation
to this is testtest
.
The embedded dynaml expression must be concatenatable with strings.
Feature state: alpha
In addition to describe conditions and loops with dynaml expressions it is also possible to use elements of the document structure to embed control structures.
Such a YAML-based control structure is always described as a map in YAML/JSON. The syntactical elements are expressed as map fields starting with <<
. Additionally, depending on the control structure, regular fields are possible. Control structures finally represent a value for the containing node. They may contain marker expressions ( <<
), also.
على سبيل المثال:
temp :
<< : (( &temporary ))
<<if : (( features("control") ))
<<then :
alice : 25
bob : 26
resolves to
final :
alice : 25
bob : 26
Tu use this alpha feature the feature flag control
must be enabled.
Please be aware: Control structure maps typically are always completely resolved before they are evaluated.
A control structure itself is always a dedicated map node in a document. It is substituted by a regular value node determined by the execution of the control structure.
The fields of a control structure map are not subject to overwriting by stubs, but the complete structure can be overwritten.
If used as value for a map field the resulting value is just used as effective value for this field.
If a map should be enriched by maps resulting from multiple control structures the special control structure <<merge:
can be used. It allows to specify a list of maps which should be merged with the actual control structure map to finally build the result value.
A control structure can be used as list value, also. In this case there is a dedicated interpretation of the resulting value of the control structure. If it is NOT a list value, for convenience, the value is directly used as list entry and substitutes the control structure map.
If the resulting value is again a list, it is inserted into the containing list at the place of occurrence of the control structure. So, if a list value should be used as dedicated entry in a list, the result of a control structure must be a list with the intended list as entry.
على سبيل المثال:
list :
- <<if : (( features("control") ))
<<then : alice
- <<if : (( features("control") ))
<<then :
- - peter
- <<if : (( features("control") ))
<<then :
- bob
resolves to
list :
- alice
- - peter
- bob
<<if:
The condition structure is defined by the syntax field <<if
. It additionally accepts the fields <<then
and <<else
.
The condition field must provide a boolean value. If it is true
the optional <<then
field is used to substitute the control structure, otherwise the optional <<else
field is used.
If the appropriate case is not specified, the result is the undefined (( ~~ ))
value. The containing field is therefore completely omitted from the output.
.eg:
x : test1
cond :
field :
<<if : (( x == "test" ))
<<then : alice
<<else : bob
evaluates cond.field
to bob
If the else case is omitted, the cond
field would be an empty map ( field
is omitted, because the contained control structure evaluates to undefined )
A comparable way to do this with regular dynaml could look like this:
cond : (( x == "test" ? "alice" :"bob" ))
A better way more suitable for complex cases would be:
local :
<< : (( &local))
then : alice
else : bob
cond : (( x == "test" ? local.then :local.else ))
<<switch:
The switch
control structure evaluates the switch value of the <<switch
field to a string and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
The nil value matches the default
case. If the switch value is undefined the control evaluates to the undefined value (( ~~ ))
.
على سبيل المثال:
x : alice
value :
<<switch : (( x ))
alice : 25
bob : 26
<<default : other
evaluates value
to 25
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
alice : 25
bob : 26
default : other
value : (( local.cases[x] || local.default ))
<<type:
The type
control structure evaluates the type of the value of the <<type
field and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
على سبيل المثال:
x : alice
value :
<<type : (( x ))
string : alice
<<default : unknown
evaluates value
to alice
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
string : alice
default : unknown
value : (( local.cases[type(x)] || local.default ))
For more complex scenarios not only switching on strings a second syntax can be used. Instead of using fields in the control map as cases, a dedicated field <<cases
may contain a list of cases, that are checked sequentially (In this flavor regular fields are not allowed anymore).
Every case is described again by a map containing the fields:
case
: the expected value to match the switch valuematch
: a lambda function taking one argument and yielding a boolean value used to match the given switch valuevalue
: (optional) the resulting value in case of a match. If not defined the result will be the undefined value. One of case
or match
must be present.
على سبيل المثال:
x : 5
selected :
<<switch : (( x ))
<<cases :
- case :
alice : 25
value : alice
- match : (( |v|->v == 5 ))
value : bob
<<default : unknown
resolves to
x : 5
selected : bob
If x
would be set to the complex value
x :
alice : 25
it would resolve to
x :
alice : 25
selected : alice
<<for:
The loop control is able to execute a multi-dimensional loop and produce a list or map based on the value combinations.
The loop ranges are specified by the value of the <<for
field. It is possible ol loop over lists or maps. The range specification can be given by either a map or list:
map : the keys of the map are the names of the control variables and the values must be lists or maps specifying the ranges.
The map key might optionally be a comma-separated pair (for example key,value
) of variable names. In this case the first name is the name for the index variable and the second one for the value variable.
If multiple ranges are specified iterations are alphabetically ordered by value variable name (first) and index variable name (second) to determine the traversing order.
list : if the control variables are defined by a list, each list element must contain two mandatory and one optional field(s):
name
: the name of the (list or map entry value) control variablevalues
: a list to define the value range.index
: (optional) the name of the variable providing the list index or map key of the loop range (defaulted to index-<name>
)Here the order in the list determine the traversal order.
Traversal is done by recursively iterating follow up ranges for every entry in the actual range. This means the last range is completely iterated for the first values of the first ranges first.
If no index variable is specified for a loop range there is an additional implicit binding for every control variable describing the actual list index or map key of the processed value for this dimension. It is denoted by index-<control variable>
If multiple loop ranges are specified, the ranges may mix iterations over maps and lists.
The iteration result value is determined by the value of the <<do
field. It is implicitly handled as template and is evaluated for every set of iteration values.
The result of the evaluation using only the <<do
value field is a list.
على سبيل المثال:
alice :
- a
- b
bob :
- 1
- 2
- 3
list :
<<for :
key,alice : (( .alice )) # sorted by using alice as primary sort key
bob : (( .bob ))
<<do :
value : (( alice "-" key "-" bob "-" index-bob ))
evaluates list
to
list :
- value : a-0-1-0
- value : a-0-2-1
- value : a-0-3-2
- value : b-1-1-0
- value : b-1-2-1
- value : b-1-3-2
It first iterates over the values for alice
. For each such value it then iterates over the values of bob
.
A comparable way to do this with regular dynaml could look like this:
list : (( sum[alice|[]|s,key,alice|-> s sum[bob|[]|s,index_bob,bob|->s (alice "-" key "-" bob "-" index_bob)]] ))
A result list may omit entries if the value expression evaluates to the undefined value ( ~~
). The nil value ( ~
) is kept. This way a for
control can be used to filter lists.
على سبيل المثال:
bob :
- 1
- 2
- 3
filtered :
<<for :
bob : (( .bob ))
<<do : (( bob == 2 ? ~~ :bob ))
resolves to
bob :
- 1
- 2
- 3
filtered :
- 1
- 3
If the result should be a map it is required to additionally specify a key value for every iteration. This is specified by the optional <<mapkey
field. Like the <<do
field it is implicitly handled as template and re-evaluated for every iteration.
على سبيل المثال:
x : suffix
alice :
- a
- b
bob :
- 1
- 2
- 3
map :
<<for :
- name : alice
values : (( .alice ))
- name : bob
values : (( .bob ))
<<mapkey : (( alice bob ))
<<do :
value : (( alice bob x ))
evaluates the field map
to
map :
a1 :
value : a1suffix
a2 :
value : a2suffix
a3 :
value : a3suffix
b1 :
value : b1suffix
b2 :
value : b2suffix
b3 :
value : b3suffix
Here the traversal order is irrelevant as long as the generated key values are unique. If several evaluations of the key expression yield the same value the last one will win.
A comparable way to do this with regular dynaml could look like this:
map : (( sum[alice|{}|s,index_alice,alice|-> s sum[bob|{}|s,index_bob,bob|->s {(alice bob)=alice bob x}]] ))
An iteration value is ignored if the key or the value evaluate to the undefined value (( ~~ ))
. Additionally the key may evaluate to the nil value (( ~ ))
, also.
على سبيل المثال:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
أو
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( bob == 2 ? ~~ :key ))
<<do : (( bob ))
resolve to
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
b1 : 1
b3 : 3
<<merge:
With merge
it is possible to merge maps given as list value of the <<merge
field with regular map fields from the control structure to determine the final map value.
The value for <<merge:
may be a single map or a list of maps to join with the directly given fields.
على سبيل المثال:
map :
<<merge :
- bob : 26
charlie : 1
- charlie : 27
alice : 25
charlie : 2
resolves to
map :
alice : 25
bob : 26
charlie : 27
If multiple maps contain the same key, the last value (in order of list) will win.
This might be combined with other control structures, for example to conditionally merge multiple maps:
على سبيل المثال:
x : charlie
map :
<<merge :
- <<if : (( x == "charlie" ))
<<then :
charlie : 27
- <<if : (( x == "alice" ))
<<then :
alice : 20
alice : 25
charlie : 2
resolves to
x : charlie
map :
alice : 25
charlie : 27
By default spiff
performs a deep structural merge of its first argument, the template file, with the given stub files. The merge is processed from right to left, providing an intermediate merged stub for every step. This means, that for every step all expressions must be locally resolvable.
Structural merge means, that besides explicit dynaml merge
expressions, values will be overridden by values of equivalent nodes found in right-most stub files. In general, flat value lists are not merged. Only lists of maps can be merged by entries in a stub with a matching index.
There is a special support for the auto-merge of lists containing maps, if the maps contain a name
field. Hereby the list is handled like a map with entries according to the value of the list entries' name
field. If another key field than name
should be used, the key field of one list entry can be tagged with the prefix key:
to indicate the indended key name. Such tags will be removed for the processed output.
In general the resolution of matching nodes in stubs is done using the same rules that apply for the reference expressions (( foo.bar.[1].baz )).
For example, given the file template.yml :
foo :
- name : alice
bar : template
- name : bob
bar : template
plip :
- id : 1
plop : template
- id : 2
plop : template
bar :
- foo : template
list :
- a
- b
and file stub.yml :
foo :
- name : bob
bar : stub
plip :
- key:id : 1
plop : stub
bar :
- foo : stub
list :
- c
- d
spiff merge template.yml stub.yml
يعود
foo :
- bar : template
name : alice
- bar : stub
name : bob
plip :
- id : 1
plop : stub
- id : 2
plop : template
bar :
- foo : stub
list :
- a
- b
Be careful that any name:
key in the template for the first element of the plip
list will defeat the key:id: 1
selector from the stub. When a name
field exist in a list element, then this element can only be targeted by this name. When the selector is defeated, the resulting value is the one provided by the template.
Merging the following files in the given order
deployment.yml
networks : (( merge ))
cf.yml
utils : (( merge ))
network : (( merge ))
meta : (( merge ))
networks :
- name : cf1
<< : (( utils.defNet(network.base.z1,meta.deployment_no,30) ))
- name : cf2
<< : (( utils.defNet(network.base.z2,meta.deployment_no,30) ))
infrastructure.yml
network :
size : 16
block_size : 256
base :
z1 : 10.0.0.0
z2 : 10.1.0.0
rules.yml
utils :
defNet : (( |b,n,s|->(*.utils.network).net ))
network :
<< : (( &template ))
start : (( b + n * .network.block_size ))
first : (( start + ( n == 0 ? 2 :0 ) ))
lower : (( n == 0 ? [] :b " - " start - 1 ))
upper : (( start + .network.block_size " - " max_ip(net.subnets.[0].range) ))
net :
subnets :
- range : (( b "/" .network.size ))
reserved : (( [] lower upper ))
static :
- (( first " - " first + s - 1 ))
instance.yml
meta :
deployment_no : 1
will yield a network setting for a dedicated deployment
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0 - 10.0.255.255
static :
- 10.0.1.0 - 10.0.1.29
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.0.0 - 10.1.0.255
- 10.1.2.0 - 10.1.255.255
static :
- 10.1.1.0 - 10.1.1.29
Using the same config for another deployment of the same type just requires the replacement of the instance.yml
. Using a different instance.yml
meta :
deployment_no : 0
will yield a network setting for a second deployment providing the appropriate settings for a unique other IP block.
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.1.0 - 10.0.255.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.1.0 - 10.1.255.255
static :
- 10.1.0.2 - 10.1.0.31
If you move to another infrastructure you might want to change the basic IP layout. You can do it just by adapting the infrastructure.yml
network :
size : 17
block_size : 128
base :
z1 : 10.0.0.0
z2 : 10.0.128.0
Without any change to your other settings you'll get
networks :
- name : cf1
subnets :
- range : 10.0.0.0/17
reserved :
- 10.0.0.128 - 10.0.127.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.0.128.0/17
reserved :
- 10.0.128.128 - 10.0.255.255
static :
- 10.0.128.2 - 10.0.128.31
There are several scenarios yielding results that do not seem to be obvious. Here are some typical pitfalls.
The auto merge never adds nodes to existing structures
For example, merging
template.yml
foo :
alice : 25
مع
stub.yml
foo :
alice : 24
bob : 26
الغلة
foo :
alice : 24
Use <<: (( merge )) to change this behaviour, or explicitly add desired nodes to be merged:
template.yml
foo :
alice : 25
bob : (( merge ))
Simple node values are replaced by values or complete structures coming from stubs, structures are deep merged.
For example, merging
template.yml
foo : (( ["alice"] ))
مع
stub.yml
foo :
- peter
- paul
الغلة
foo :
- peter
- paul
But the template
foo : [ (( "alice" )) ]
is merged without any change.
Expressions are subject to be overridden as a whole
A consequence of the behaviour described above is that nodes described by an expession are basically overridden by a complete merged structure, instead of doing a deep merge with the structues resulting from the expression evaluation.
For example, merging
template.yml
men :
- bob : 24
women :
- alice : 25
people : (( women men ))
مع
stub.yml
people :
- alice : 13
الغلة
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
To request an auto-merge of the structure resulting from the expression evaluation, the expression has to be preceeded with the modifier prefer
( (( prefer women men ))
). This would yield the desired result:
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
- bob : 24
Nested merge expressions use implied redirections
merge
expressions implicity use a redirection implied by an outer redirecting merge. في المثال التالي
meta :
<< : (( merge deployments.cf ))
properties :
<< : (( merge ))
alice : 42
the merge expression in meta.properties
is implicity redirected to the path deployments.cf.properties
implied by the outer redirecting merge
. Therefore merging with
deployments :
cf :
properties :
alice : 24
bob : 42
الغلة
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
على سبيل المثال:
pot : (( lambda |x,y|-> y == 0 ? 1 :(|m|->m * m)(_(x, y / 2)) * ( 1 + ( y % 2 ) * ( x - 1 ) ) ))
seq : (( lambda |b,l|->map[l|x|-> .pot(b,x)] ))
values : (( .seq(2,[ 0..4 ]) ))
yields the list [ 1,2,4,8,16 ]
for the property values
.
Functions can be used to parameterize templates
The combination of functions with templates can be use to provide functions yielding complex structures. The parameters of a function are part of the scope used to resolve reference expressions in a template used in the function body.
على سبيل المثال:
relation :
template :
<< : (( &template ))
bob : (( x " " y ))
relate : (( |x,y|->*relation.template ))
banda : (( relation.relate("loves","alice") ))
evaluates to
relation :
relate : lambda|x,y|->*(relation.template)
template :
<< : (( &template ))
bob : (( x " " y ))
banda :
bob : loves alice
Scopes can be used to parameterize templates
Scope literals are also considered when instantiating templates. Therefore they can be used to set explicit values for relative reference expressions used in templates.
على سبيل المثال:
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped : (( ( $alice = 25, "bob" = 26 ) *template ))
evaluates to
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped :
sum : 51
Aggregations may yield complex values by using templates
The expression of an aggregation may return complex values by returning inline lists or instantiated templates. The binding of the function will be available (as usual) for the evaluation of the template. In the example below the aggregation provides a map with both the sum and the product of the list entries containing the integers from 1 to 4.
على سبيل المثال:
sum : (( sum[[1..4]|init|s,e|->*temp] ))
temp :
<< : (( &template ))
sum : (( s.sum + e ))
prd : (( s.prd * e ))
init :
sum : 0
prd : 1
yields for sum
the value
sum:
prd: 24
sum: 10
Taking advantage of the undefined value
At first glance it might look strange to introduce a value for undefined . But it can be really useful as will become apparent with the following examples.
Whenever a stub syntactically defines a field it overwrites the default in the template during merging. Therefore it would not be possible to define some expression for that field that eventually keeps the default value. Here the undefined value can help:
eg: merging
template.yml
alice : 24
bob : 25
مع
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
الغلة
alice : ~
bob : 25
There is a problem accessing upstream values. This is only possible if the local stub contains the definition of the field to use. But then there will always be a value for this field, even if the upstream does not overwrite it.
Here the undefined value can help by providing optional access to upstream values. Optional means, that the field is only defined, if there is an upstream value. Otherwise it is undefined for the expressions in the local stub and potential downstream templates. This is possible because the field is formally defined, and will therefore be merged, only after evaluating the expression if it is not merged it will be removed again.
eg: merging
template.yml
alice : 24
bob : 25
peter : 26
مع
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
و
config.yml
config :
alice : 4711
peter : 0815
الغلة
alice : 4711 # transferred from config's config value
bob : 25 # kept default value, because not set in config.yml
peter : 26 # kept, because mapping source not available in mapping.yml
This can be used to add an intermediate stub, that offers a dedicated configuration interface and contains logic to map this interface to a manifest structure already defining default values.
Templates versus map literals
As described earlier templates can be used inside functions and mappings to easily describe complex data structures based on expressions refering to parameters. Before the introduction of map literals this was the only way to achieve such behaviour. The advantage is the possibility to describe the complex structure as regular part of a yaml document, which allows using the regular yaml formatting facilitating readability.
على سبيل المثال:
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ *templates.job ] ] ))
templates :
job :
<< : (( &template ))
name : (( k ))
instances : (( v ))
evaluates to
scaling :
runner_z1 : 10
router_z1 : 4
jobs :
- instances : 4
name : router_z1
- instances : 10
name : runner_z1
...
With map literals this construct can significantly be simplified
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ {"name"=k, "value"=v} ] ] ))
Nevertheless the first, template based version might still be useful, if the data structures are more complex, deeper or with complex value expressions. For such a scenario the description of the data structure as template should be preferred. It provides a much better readability, because every field, list entry and value expression can be put into dedicated lines.
But there is still a qualitative difference. While map literals are part of a single expression always evaluated as a whole before map fields are available for referencing, templates are evaluated as regular yaml documents that might contain multiple fields with separate expressions referencing each other.
على سبيل المثال:
range : (( (|cidr,first,size|->(*templates.addr).range)("10.0.0.0/16",10,255) ))
templates :
addr :
<< : (( &template ))
base : (( min_ip(cidr) ))
start : (( base + first ))
end : (( start + size - 1 ))
range : (( start " - " end ))
evaluates range
to
range : 10.0.0.10 - 10.0.1.8
...
Defaulting and Requiring Fields
Traditionally defaulting in spiff is done by a downstream template where the playload data file is used as stub.
Fields with simple values can just be specified with their values. They will be overwritten by stubs using the regular spiff document merging mechanisms.
It is more difficult for maps or lists. If a map is specified in the template only its fields will be merged (see above), but it is never replaced as a whole by settings in the playload definition files. And Lists are never merged.
Therefore maps and lists that should be defaulted as a whole must be specified as initial expressions (referential or inline) in the template file.
eg: merging of
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
و
payload.yaml
config :
value2 : configured
othervalue : I want this but don't get it
evaluates to
config :
person :
age : bob
name : alice
value1 : defaultvalue
value2 : configured
In such a scenario the structure of the resulting document is defined by the template. All kinds of variable fields or sub-structures must be forseen by the template by using <<: (( merge ))
expressions in maps.
eg: changing template to
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
<< : (( merge ))
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
Known optional fields can be described using the undefined ( ~~
) expression:
template.yaml
config :
optional : (( ~~ ))
Such fields will only be part of the final document if they are defined in an upstream stub, otherwise they will be completely removed.
Required fields can be defined with the expression (( merge ))
. If no stub contains a value for this field, the merge cannot be fullfilled and an error is reported. If a dedicated message should be shown instead, the merge expression can be defaulted with an error function call.
على سبيل المثال:
template.yaml
config :
password : (( merge || error("the field password is required") ))
will produce the following error if no stub contains a value:
error generating manifest: unresolved nodes:
(( merge || error("the field password is required") )) in c.yaml config.password () *the field password is required
This can be simplified by reducing the expression to the sole error
expression.
Besides this template based defaulting it is also possible to provide defaults by upstream stubs using the &default
marker. Here the payload can be a downstream file.
X509 and providing State
When generating keys or certificates with the X509 Functions there will be new keys or certificates for every execution of spiff . But it is also possible to use spiff to maintain key state. A very simple script could look like this:
#! /bin/bash
DIR= " $( dirname " $0 " ) /state "
if [ ! -f " $DIR /state.yaml " ] ; then
echo " state: " > " $DIR /state.yaml "
fi
spiff merge " $DIR /template.yaml " " $DIR /state.yaml " > " $DIR /. $$ " && mv " $DIR /. $$ " " $DIR /state.yaml "
It uses a template file (containing the rules) and a state file with the actual state as stub. The first time it is executed there is an empty state and the rules are not overridden, therefore the keys and certificates are generated. Later on, only additional new fields are calculated, the state fields already containing values just overrule the dynaml expressions for those fields in the template.
If a re-generation is required, the state file can just be deleted.
A template may look like this:
state/template.yaml
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : rootca
privateKey : (( state.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
peer :
organization : Mandelsoft
commonName : etcd
publicKey : (( state.pub ))
caCert : (( state.cacert ))
caPrivateKey : (( state.cakey ))
validity : 100
usage :
- ServerAuth
- ClientAuth
- KeyEncipherment
hosts :
- etcd.mandelsoft.org
state :
cakey : (( x509genkey(2048) ))
capub : (( x509publickey(cakey) ))
cacert : (( x509cert(spec.ca) ))
key : (( x509genkey(2048) ))
pub : (( x509publickey(key) ))
peer : (( x509cert(spec.peer) ))
The merge then generates a rootca and some TLS certificate signed with this CA.
Generating, Deploying and Accessing Status for Kubernetes Resources
The sync
function offers the possibility to synchronize the template processing with external content. This can also be the output of a command execution. Therefore the template processing can not only be used to generate a deployment manifest, but also for applying this to a target system and retrieving deployment status values for the further processing.
A typical scenario of this kind could be a kubernetes setup including a service of type LoadBalancer . Once deployed it gets assigned status information about the IP address or hostname of the assigned load balancer. This information might be required for some other deployment manifest.
A simple template for such a deployment could like this:
service :
apiVersion : v1
kind : Service
metadata :
annotations :
dns.mandelsoft.org/dnsnames : echo.test.garden.mandelsoft.org
dns.mandelsoft.org/ttl : " 500 "
name : test-service
namespace : default
spec :
ports :
- name : http
port : 80
protocol : TCP
targetPort : 8080
sessionAffinity : None
type : LoadBalancer
deployment :
testservice : (( sync[pipe_uncached(service, "kubectl", "apply", "-f", "-", "-o", "yaml")|value|->defined(value.status.loadBalancer.ingress)] ))
otherconfig :
lb : (( deployment.testservice.status.loadBalancer.ingress ))
Crazy Shit: Graph Analaysis with spiff
It is easy to describe a simple graph with knots and edges (for example for a set of components and their dependencies) just by using a map of lists.
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
Now it would be useful to figure out whether there are dependency cycles or to determine ordered transitive dependencies for a component.
Let's say something like this:
graph :
utilities :
closures : (( utilities.graph.evaluate(graph) ))
cycles : (( utilities.graph.cycles(closures) ))
Indeed, this can be done with spiff. The only thing required is a "small utilities stub" .
utilities :
<< : (( &temporary ))
graph :
_dep : (( |model,comp,closure|->contains(closure,comp) ? { $deps=[], $err=closure [comp]} :($deps=_._deps(model,comp,closure [comp]))($err=sum[deps|[]|s,e|-> length(s) >= length(e.err) ? s :e.err]) { $deps=_.join(map[deps|e|->e.deps]), $err=err} ))
_deps : (( |model,comp,closure|->map[model.[comp]|dep|->($deps=_._dep(model,dep,closure)) { $deps=[dep] deps.deps, $err=deps.err }] ))
join : (( |lists|->sum[lists|[]|s,e|-> s e] ))
min : (( |list|->sum[list|~|s,e|-> s ? e < s ? e :s :e] ))
normcycle : (( |cycle|->($min=_.min(cycle)) min ? sum[cycle|cycle|s,e|->s.[0] == min ? s :(s.[1..] [s.[1]])] :cycle ))
cycle : (( |list|->list ? ($elem=list.[length(list) - 1]) _.normcycle(sum[list|[]|s,e|->s ? s [e] :e == elem ? [e] :s]) :list ))
norm : (( |deps|->{ $deps=_.reverse(uniq(_.reverse(deps.deps))), $err=_.cycle(deps.err) } ))
reverse : (( |list|->sum[list|[]|s,e|->[e] s] ))
evaluate : (( |model|->sum[model|{}|s,k,v|->s { k=_.norm(_._dep(model,k,[]))}] ))
cycles : (( |result|->uniq(sum[result|[]|s,k,v|-> v.err ? s [v.err] :s]) ))
And magically spiff does the work just by calling
spiff merge closure.yaml graph.yaml utilities.yaml
closures :
a :
deps :
- c
- b
- a
err :
- a
- c
- a
b :
deps : []
err : []
c :
deps :
- a
- b
- c
err :
- a
- c
- a
d :
deps :
- b
err : []
e :
deps :
- d
- b
err : []
cycles :
- - a
- c
- a
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
The evaluation of dynaml expressions may fail because of several reasons:
If a dynaml expression cannot be resolved to a value, it is reported by the spiff merge
operation using the following layout:
(( <failed expression> )) in <file> <path to node> (<referred path>) <tag><issue>
(( min_ip("10") )) in source.yml node.a.[0] () *CIDR argument required
Cyclic dependencies are detected by iterative evaluation until the document is unchanged after a step. Nodes involved in a cycle are therefore typically reported just as unresolved node without a specific issue.
The order of the reported unresolved nodes depends on a classification of the problem, denoted by a dedicated tag. The following tags are used (in reporting order):
علامة | معنى |
---|---|
* | error in local dynaml expression |
@ | dependent or involved in cyclic dependencies |
- | subsequent error because of refering to a yaml node with an error |
Problems occuring during inline template processing are reported as nested problems. The classification is propagated to the outer node.
If a problem occurs in nested lamba calls the call stack together with the lamba function and is local binding is listed.
(( 2 + .func(2) )) in local/err.yaml value () *evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 2}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 1}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 0}
... resolution of template 'template' failed
(( z )) in local/err.yaml val ()*'z' not found
In case of parsing errors in dynaml expressions, the error location is shown now. If it is a multi line expression the line a character/symbol number in that line is show, otherwise the line numer is omitted.
((
2 ++ .func(2)
)) in local/err.yaml faulty () *parse error near line 2 symbol 2 - line 2 symbol 3: " "
Spiff provides a Go package ( spiffing
) that can be used to include spiff templates in Go programs.
An example program could look like this:
import (
"fmt"
"math"
"os"
"github.com/mandelsoft/spiff/dynaml"
"github.com/mandelsoft/spiff/spiffing"
)
func func_pow ( arguments [] interface {}, binding dynaml. Binding ) ( interface {}, dynaml. EvaluationInfo , bool ) {
info := dynaml . DefaultInfo ()
if len ( arguments ) != 2 {
return info . Error ( "pow takes 2 arguments" )
}
a , b , err := dynaml . NumberOperands ( arguments [ 0 ], arguments [ 1 ])
if err != nil {
return info . Error ( "%s" , err )
}
_ , i := a .( int64 )
if i {
r := math . Pow ( float64 ( a .( int64 )), float64 ( b .( int64 )))
if float64 ( int64 ( r )) == r {
return int64 ( r ), info , true
}
return r , info , true
} else {
return math . Pow ( a .( float64 ), b .( float64 )), info , true
}
}
var state = `
state: {}
`
var stub = `
unused: (( input ))
ages:
alice: (( pow(2,5) ))
bob: (( alice + 1 ))
`
var template = `
state:
<<<: (( &state ))
random: (( rand("[:alnum:]", 10) ))
ages: (( &temporary ))
example:
name: (( input )) # direct reference to additional values
sum: (( sum[ages|0|s,k,v|->s + v] ))
int: (( pow(2,4) ))
float: 2.1
pow: (( pow(1.1e1,2.1) ))
`
func Error ( err error ) {
if err != nil {
fmt . Fprintf ( os . Stderr , "Error: %s n " , err )
os . Exit ( 1 )
}
}
func main () {
values := map [ string ] interface {}{}
values [ "input" ] = "this is an input"
functions := spiffing . NewFunctions ()
functions . RegisterFunction ( "pow" , func_pow )
spiff , err := spiffing . New (). WithFunctions ( functions ). WithValues ( values )
Error ( err )
pstate , err := spiff . Unmarshal ( "state" , [] byte ( state ))
Error ( err )
pstub , err := spiff . Unmarshal ( "stub" , [] byte ( stub ))
Error ( err )
ptempl , err := spiff . Unmarshal ( "template" , [] byte ( template ))
Error ( err )
result , err := spiff . Cascade ( ptempl , []spiffing. Node { pstub }, pstate )
Error ( err )
b , err := spiff . Marshal ( result )
Error ( err )
newstate , err := spiff . Marshal ( spiff . DetermineState ( result ))
Error ( err )
fmt . Printf ( "==== new state === n " )
fmt . Printf ( "%s n " , string ( newstate ))
fmt . Printf ( "==== result === n " )
fmt . Printf ( "%s n " , string ( b ))
}
وهو يدعم