_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
adalah cabang dari spiff yang menyediakan ekstensi yang kompatibel untuk spiff berdasarkan versi terbaru yang menawarkan serangkaian fitur baru yang belum tersedia di spiff. Semua perbaikan yang disediakan oleh proyek spiff asli juga akan dimasukkan ke dalam spiff++. Karena tidak akan ada jalan kembali ke basis sumber spiff, repositori spiff++ independen baru telah dibuat untuk melanjutkan pengembangan spiff++.spiff adalah alat baris perintah dan sistem templating YAML hibrid dalam domain deklaratif. Sementara sistem templating reguler memproses file templat dengan mengganti ekspresi templat dengan nilai yang diambil dari sumber data eksternal, dalam domain berarti mesin templating mengetahui sintaksis dan struktur templat yang diproses. Oleh karena itu, ia dapat mengambil nilai untuk ekspresi templat langsung dari dokumen yang diproses, termasuk bagian yang dilambangkan dengan ekspresi templat itu sendiri.
Misalnya:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
Daripada hanya menggunakan sumber nilai eksternal, spiff menyediakan mekanisme penggabungan untuk menggabungkan template dengan sejumlah stub penggabungan untuk menghasilkan dokumen akhir.
Ini adalah alat baris perintah dan sistem templating YAML deklaratif, yang dirancang khusus untuk menghasilkan manifes penerapan (misalnya manifes BOSH, Kubernetes, atau Landscaper).
Selain CLI, terdapat perpustakaan golang yang memungkinkan penggunaan pemrosesan template spiff di program GO apa pun (misalnya Landscaper).
Mesin templating menawarkan pengaktifan akses ke sistem file berdasarkan sistem file virtual yang dapat dikonfigurasi atau sistem proses untuk menjalankan perintah dan memasukkan output ke dalam pemrosesan templat.
Isi:
<<if:
<<switch:
<<type:
<<for:
<<merge:
Biner yang dapat dieksekusi rilis resmi dapat diunduh melalui rilis Github untuk mesin Darwin, Linux dan PowerPC (dan mesin virtual).
Beberapa dependensi spiff telah berubah sejak rilis resmi terakhir, dan spiff tidak akan diperbarui untuk mengimbangi dependensi ini. Ketergantungan tersebut diperbaiki atau disalin ke basis kode lokal.
spiff merge template.yml [template2.yml ...]
Gabungkan sekumpulan file templat menjadi satu manifes, cetaklah.
Lihat 'bahasa templating dinamis' untuk detail file templat, atau contoh/subdir untuk contoh yang lebih rumit.
Contoh:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
Dimungkinkan untuk membaca satu file dari input standar dengan menggunakan nama file -
. Ini hanya dapat digunakan sekali. Hal ini memungkinkan penggunaan spiff sebagai bagian dari pipeline untuk hanya memproses satu aliran atau memproses aliran berdasarkan beberapa templat/stub.
File templat (argumen pertama) dapat berupa aliran beberapa dokumen yang berisi beberapa dokumen YAML yang dipisahkan oleh baris yang hanya berisi ---
. Setiap dokumen YAML akan diproses secara independen dengan file rintisan yang diberikan. Hasilnya adalah aliran dokumen yang diproses dalam urutan yang sama. Jika simpul akar dokumen ditandai sebagai sementara, dokumen tersebut dihilangkan dari aliran keluaran. Misalnya, ini dapat digunakan untuk menghasilkan manifes kubernetes untuk digunakan oleh kubectl
.
Perintah merge
menawarkan beberapa opsi:
Opsi --partial
. Jika opsi ini diberikan spiff menangani evaluasi ekspresi yang tidak lengkap. Semua kesalahan diabaikan dan bagian dokumen yaml yang tidak dapat diselesaikan dikembalikan sebagai string.
Dengan opsi --json
outputnya akan berformat JSON, bukan YAML.
Opsi --path <path>
dapat digunakan untuk menampilkan jalur bersarang, alih-alih dokumen lengkap yang diproses.
Jika keluarannya berupa daftar, opsi --split
menampilkan setiap elemen daftar sebagai dokumen terpisah. Format yaml digunakan seperti biasa ---
sebagai garis pemisah. Format json menghasilkan urutan dokumen json , satu per baris.
Dengan --select <field path>
dimungkinkan untuk memilih bidang khusus dari dokumen yang diproses untuk keluaran
Dengan --evaluate <dynaml expression>
dimungkinkan untuk mengevaluasi ekspresi dinamis tertentu pada dokumen yang diproses untuk keluarannya. Ekspresi tersebut dievaluasi sebelum jalur seleksi diterapkan, yang kemudian akan bekerja pada hasil evaluasi.
Opsi --state <path>
mengaktifkan dukungan negara untuk spiff . Jika file yang diberikan ada, maka file tersebut diletakkan di atas daftar stub yang dikonfigurasi untuk file yang diberikan, file tersebut ditempatkan di atas daftar rintisan yang dikonfigurasi untuk pemrosesan penggabungan. Selain keluaran dokumen yang diproses, dokumen tersebut difilter untuk node yang ditandai dengan penanda &state
. Dokumen yang difilter ini kemudian disimpan di bawah file yang dilambangkan, menyimpan file status lama dengan akhiran .bak
. Ini dapat digunakan bersama dengan penggabungan manual seperti yang ditawarkan oleh perpustakaan utilitas negara.
Dengan opsi --bindings <path>
file yaml dapat ditentukan, yang kontennya digunakan untuk membuat pengikatan tambahan untuk pemrosesan. Dokumen yaml harus terdiri dari peta. Setiap kunci digunakan sebagai pengikatan tambahan. Dokumen pengikatan tidak diproses, nilainya digunakan sebagaimana ditentukan.
Dengan opsi --tag <tag>:<path>
file yaml dapat ditentukan, yang kontennya digunakan sebagai nilai untuk tag global yang telah ditentukan sebelumnya (lihat Tag). Tag dapat diakses dengan ekspresi referensi dalam bentuk <tag>::<ref>
. Berbeda dengan konten yang diberi tag binding tidak bersaing dengan node dalam dokumen, konten tersebut menggunakan namespace referensi lain.
Dengan opsi --define <key>=<value>
(singkatan -D
) nilai pengikatan tambahan dapat ditentukan pada baris perintah yang mengesampingkan nilai pengikatan dari file pengikatan. Opsi ini dapat terjadi berkali-kali.
Jika kunci berisi titik ( .
), maka akan ditafsirkan sebagai ekspresi jalur untuk mendeskripsikan bidang dalam nilai peta yang dalam. Sebuah titik (dan sebelum sebuah titik) dapat di-escape dengan
untuk menyimpannya dalam nama bidang.
Opsi --preserve-escapes
akan mempertahankan pelolosan untuk ekspresi dinamis dan arahan penggabungan daftar/peta. Opsi ini dapat digunakan jika ingin dilakukan langkah pemrosesan lebih lanjut dari suatu hasil pemrosesan dengan spiff .
Opsi --preserve-temporary
akan mempertahankan kolom yang ditandai sebagai sementara di dokumen akhir.
Opsi --features=<featurelist>
akan mengaktifkan fitur yang diberikan ini. Fitur baru yang tidak kompatibel dengan perilaku lama harus diaktifkan secara eksplisit. Biasanya fitur tersebut tidak menghentikan perilaku umum tetapi memperkenalkan interpretasi khusus untuk nilai yaml yang sebelumnya digunakan sebagai nilai reguler.
Perpustakaan folder menawarkan beberapa perpustakaan utilitas yang berguna. Mereka juga dapat dijadikan contoh kehebatan mesin templating ini.
spiff diff manifest.yml other-manifest.yml
Tunjukkan perbedaan struktural antara dua manifes penerapan. Di sini aliran dengan banyak dokumen juga didukung. Untuk menunjukkan tidak adanya perbedaan, jumlah dokumen pada kedua aliran harus sama dan setiap dokumen pada aliran pertama tidak boleh memiliki perbedaan dibandingkan dengan dokumen dengan indeks yang sama pada aliran kedua. Perbedaan yang ditemukan ditampilkan untuk setiap dokumen secara terpisah.
Tidak seperti alat diffing dasar dan bahkan bosh diff
, perintah ini memiliki pengetahuan semantik tentang manifes penerapan, dan tidak hanya berbasis teks. Misalnya, jika dua manifes adalah sama kecuali mereka memiliki beberapa pekerjaan yang terdaftar dalam urutan yang berbeda, spiff diff
akan mendeteksi hal ini, karena perintah pekerjaan penting dalam sebuah manifes. Di sisi lain, jika dua manifes hanya berbeda dalam urutan kumpulan sumber dayanya, misalnya, maka manifes tersebut akan menghasilkan dan mengosongkan diff karena urutan kumpulan sumber daya sebenarnya tidak penting untuk penerapan.
Juga tidak seperti bosh diff
, perintah ini tidak mengubah file mana pun.
Ini dirancang untuk memeriksa perbedaan antara satu penerapan dan penerapan berikutnya.
Aliran khas:
$ 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
Perintah convert
sub dapat digunakan untuk mengonversi file input ke json atau hanya untuk menormalkan urutan kolom. Opsi yang tersedia adalah --json
, --path
, --split
atau --select
sesuai dengan artinya untuk sub perintah merge
.
spiff encrypt secret.yaml
Sub perintah encrypt
dapat digunakan untuk mengenkripsi atau mendekripsi data sesuai dengan fungsi encrypt
dynaml. Kata sandi dapat diberikan sebagai argumen kedua atau diambil dari variabel lingkungan SPIFF_ENCRYPTION_KEY
. Argumen terakhir dapat digunakan untuk meneruskan metode enkripsi (lihat fungsi encrypt
)
Data diambil dari file yang ditentukan. Jika -
diberikan, maka dibaca dari stdin.
Jika opsi -d
diberikan, data didekripsi, jika tidak, data dibaca sebagai dokumen yaml dan hasil terenkripsi akan dicetak.
Fitur baru yang tidak kompatibel dengan perilaku lama harus diaktifkan secara eksplisit. Biasanya fitur tersebut tidak menghentikan perilaku umum namun memperkenalkan interpretasi khusus untuk nilai yaml yang sebelumnya digunakan sebagai nilai reguler dan oleh karena itu dapat merusak kasus penggunaan yang ada.
Tanda fitur berikut saat ini didukung:
Fitur | Sejak | Negara | Arti |
---|---|---|---|
interpolation | 1.7.0-beta-1 | alfa | dynaml sebagai bagian dari string yaml |
control | 1.7.0-beta-4 | alfa | struktur kontrol berbasis yaml |
Tanda fitur aktif dapat ditanyakan menggunakan fungsi dynaml features()
sebagai daftar string. Jika fungsi ini dipanggil dengan argumen string, ia akan mengembalikan apakah fitur yang diberikan saat ini diaktifkan.
Fitur dapat diaktifkan melalui baris perintah menggunakan opsi --features
, melalui pustaka go menggunakan fungsi WithFeatures
, atau secara umum dengan menyetel variabel lingkungan SPIFF_FEATURES
ke daftar fitur. Pengaturan ini selalu digunakan sebagai default. Dengan menggunakan pengaturan spiff Plain()
dari perpustakaan go, semua variabel lingkungan diabaikan.
Suatu fitur dapat ditentukan berdasarkan nama atau berdasarkan nama yang diawali dengan awalan no
untuk menonaktifkannya.
Folder perpustakaan berisi beberapa perpustakaan template keren yang berguna. Ini pada dasarnya hanyalah stub yang ditambahkan ke daftar file gabungan untuk menawarkan fungsi utilitas untuk pemrosesan penggabungan.
Spiff menggunakan bahasa templating deklaratif dan bebas logika yang disebut 'dynaml' (yaml dinamis).
Setiap node dynaml dijamin akan diselesaikan menjadi node YAML. Ini bukan interpolasi string. Hal ini membuat pengembang tidak perlu memikirkan bagaimana suatu nilai akan dirender dalam template yang dihasilkan.
Node dynaml muncul di file .yml sebagai string yang menunjukkan ekspresi yang dikelilingi oleh dua tanda kurung (( <dynaml> ))
. Mereka dapat digunakan sebagai nilai peta atau entri dalam daftar. Ekspresinya mungkin mencakup beberapa baris. Bagaimanapun nilai string yaml tidak boleh diakhiri dengan baris baru (misalnya menggunakan |-
)
Jika nilai dalam tanda kurung tidak boleh diinterpretasikan sebagai ekspresi dinamis dan disimpan sebagaimana adanya dalam keluaran, nilai tersebut dapat di-escape dengan tanda seru langsung setelah tanda kurung buka.
Misalnya, ((! .field ))
dipetakan ke nilai string (( .field ))
dan ((!! .field ))
dipetakan ke nilai string ((! .field ))
.
Berikut ini adalah daftar lengkap ekspresi dinamis:
(( foo ))
Cari kunci 'foo' terdekat (yaitu pelingkupan leksikal) di templat saat ini dan bawa masuk.
misalnya:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
Contoh ini akan menyelesaikan:
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
Hal berikut ini tidak akan terselesaikan karena nama kunci sama dengan nilai yang akan digabungkan:
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
Cari kunci 'foo' terdekat, dan dari sana ikuti ke .bar.[1].baz
.
Jalur adalah rangkaian langkah yang dipisahkan oleh titik. Langkah bisa berupa kata untuk peta, atau angka yang diapit tanda kurung untuk pengindeksan daftar. Indeksnya mungkin negatif (minus diikuti angka). Indeks negatif diambil dari akhir daftar (indeks efektif = indeks + panjang(daftar)).
Jalur yang tidak dapat diselesaikan menyebabkan kesalahan evaluasi. Jika referensi yang diharapkan terkadang tidak diberikan, referensi tersebut harus digunakan bersama dengan '||' (lihat di bawah) untuk menjamin resolusi.
Catatan : Tata bahasa dynaml telah dikerjakan ulang untuk mengaktifkan sintaksis indeks biasa, sekarang. Dari pada foo.bar.[1]
sekarang dimungkinkan untuk menggunakan foo.bar[1]
.
Catatan : Referensi selalu ada dalam template atau stub, dan urutan tidak menjadi masalah. Anda dapat merujuk ke node dinamis lain dan menganggapnya telah terselesaikan, dan node referensi pada akhirnya akan terselesaikan setelah node dependen terselesaikan.
misalnya:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
Ini akan terselesaikan selama 'sesuatu' dapat diatasi, dan selama hal tersebut menghasilkan sesuatu seperti ini:
from :
the :
stub : foo
Jika jalur dimulai dengan titik ( .
), jalur selalu dievaluasi dari akar dokumen. Jika akar dokumen adalah sebuah daftar, tingkat peta pertama digunakan untuk menyelesaikan ekspresi jalur jika dimulai dengan .__map
. Hal ini dapat digunakan untuk menghindari kebutuhan untuk menggunakan indeks daftar sendiri (seperti .[1].path
), yang mungkin berubah jika entri daftar ditambahkan.
Entri daftar yang terdiri dari peta dengan bidang name
dapat langsung dialamatkan dengan nilai namanya sebagai komponen jalur.
Catatan : Ini juga berfungsi untuk jalur absolut untuk dokumen daftar.
misalnya:
Usia alice in
list :
- name : alice
age : 25
dapat direferensikan dengan menggunakan jalur list.alice.age
, bukan list[0].age
.
Secara default, bidang dengan name
nama digunakan sebagai bidang kunci. Jika field lain harus digunakan sebagai field kunci, maka field tersebut dapat ditandai dalam satu entri daftar sebagai kunci dengan mengawali nama field dengan kata kunci key:
. Kata kunci ini dihapus oleh pemrosesan dan tidak akan menjadi bagian dari hasil pemrosesan akhir.
misalnya:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
akan diselesaikan
list :
- person : alice
age : 25
alice :
person : alice
age : 25
Bidang kunci baru ini juga akan diamati selama penggabungan daftar.
Jika bidang kunci yang dipilih dimulai dengan !
, fitur utama dinonaktifkan. Tanda seru juga dihapus dari nama bidang efektif.
Jika nilai untuk bidang kunci tidak unik, maka bidang tersebut juga akan dinonaktifkan.
(( foo.[bar].baz ))
Cari kunci 'foo' terdekat, dan dari sana ikuti ke bidang yang dijelaskan oleh bar
ekspresi dan kemudian ke .baz.
Indeks dapat berupa konstanta bilangan bulat (tanpa spasi) seperti yang dijelaskan di bagian terakhir. Namun bisa juga berupa ekspresi dinamis arbitrer (walaupun bilangan bulat, tetapi dengan spasi). Jika ekspresi bernilai string, ekspresi tersebut akan mencari bidang khusus. Jika ekspresi bernilai integer, elemen array dengan indeks ini akan dialamatkan. Titik ( .
) di depan operator indeks bersifat opsional.
misalnya:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
Ini akan menyelesaikan foo
ke nilai 42
. Indeks dinamis mungkin juga berada di akhir ekspresi (tanpa .bar
).
Pada dasarnya ini adalah cara sederhana untuk mengekspresikan sesuatu seperti eval("values." name ".bar")
Jika ekspresi dievaluasi menjadi sebuah daftar, elemen daftar (string atau bilangan bulat) digunakan sebagai elemen jalur untuk mengakses bidang yang lebih dalam.
misalnya:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
menyelesaikan foo
lagi ke nilai 42
.
Catatan : Operator indeks juga dapat digunakan pada elemen root ( .[index]
).
Dimungkinkan untuk menentukan beberapa indeks yang dipisahkan koma ke daftar yang berurutan ( foo[0][1]
setara dengan `foo[0,1]). Dalam hal ini indeks mungkin tidak akan dicantumkan lagi.
(( list.[1..3] ))
Ekspresi irisan dapat digunakan untuk mengekstrak sub daftar khusus dari ekspresi daftar. Rentang start ..
end mengekstrak daftar panjangnya end-start+1 dengan elemen dari indeks start hingga end . Jika indeks awal negatif, potongan diambil dari akhir daftar dari length+start hingga length+end . Jika indeks akhir lebih rendah dari indeks awal, hasilnya adalah array kosong.
misalnya:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
Indeks awal atau akhir mungkin dihilangkan. Kemudian dipilih sesuai dengan ukuran sebenarnya dari daftar. Oleh karena itu list.[1..length(list)]
setara dengan list.[1..]
.
mengevaluasi foo
ke daftar [b,c]
.
(( 1.2e4 ))
Literat angka didukung untuk bilangan bulat dan nilai floating point.
(( "foo" ))
String literal. Semua pengkodean string json didukung (misalnya n
, "
atau uxxxx
).
(( [ 1, 2, 3 ] ))
Daftar secara harafiah. Elemen daftar mungkin lagi berupa ekspresi. Ada literal daftar khusus [1 .. -1]
, yang dapat digunakan untuk menyelesaikan rentang angka yang bertambah atau berkurang ke suatu daftar.
misalnya:
list : (( [ 1 .. -1 ] ))
hasil
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
Literal peta dapat digunakan untuk mendeskripsikan peta sebagai bagian dari ekspresi dinamis. Baik kunci maupun nilainya, mungkin juga berupa ekspresi, sehingga ekspresi kunci harus dievaluasi menjadi string. Dengan cara ini dimungkinkan untuk membuat peta dengan kunci non-statis. Operator penugasan =
telah dipilih daripada karakter titik dua :
biasa yang digunakan di yaml, karena hal ini akan mengakibatkan konflik dengan sintaksis yaml.
Literal peta dapat terdiri dari sejumlah tugas lapangan yang dipisahkan dengan koma ,
.
misalnya:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
hasil
name : peter
age : 23
map :
alice : {}
peter : 23
Cara lain untuk membuat daftar berdasarkan ekspresi adalah fungsi makemap
dan list_to_map
.
(( ( "alice" = 25 ) alice ))
Ekspresi apa pun dapat diawali oleh sejumlah literal cakupan eksplisit. Literal cakupan mendeskripsikan peta yang nilainya tersedia untuk resolusi referensi relatif dari ekspresi (lingkup statis). Ini menciptakan pengikatan lokal tambahan untuk nama tertentu.
Literal cakupan dapat terdiri dari sejumlah tugas lapangan yang dipisahkan dengan koma ,
. Kunci serta nilai diberikan oleh ekspresi, sedangkan ekspresi kunci harus dievaluasi menjadi string. Semua ekspresi dievaluasi dalam cakupan luar berikutnya, ini berarti pengaturan selanjutnya dalam suatu cakupan tidak dapat menggunakan pengaturan sebelumnya dalam literal cakupan yang sama.
misalnya:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
hasil
scoped : 51
Nama bidang mungkin juga dilambangkan dengan simbol ( $
nama ).
(( foo bar ))
Ekspresi penggabungan digunakan untuk menggabungkan rangkaian ekspresi dinamis.
(( "foo" bar ))
Penggabungan (di mana bar adalah expr dinamis lainnya). Urutan nilai sederhana apa pun (string, integer, dan boolean) dapat digabungkan, diberikan oleh ekspresi dinamis apa pun.
misalnya:
domain : example.com
uri : (( "https://" domain ))
Dalam contoh ini uri
akan menentukan nilai "https://example.com"
.
(( [1,2] bar ))
Penggabungan daftar sebagai ekspresi (di mana bar adalah expr dinamis lainnya). Urutan daftar apa pun dapat digabungkan, diberikan oleh ekspresi dinamis apa pun.
misalnya:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
Dalam contoh ini static_ips
akan menentukan nilai [ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
.
Jika ekspresi kedua mengevaluasi nilai selain daftar (integer, boolean, string, atau peta), nilai tersebut ditambahkan ke daftar pertama.
misalnya:
foo : 3
bar : (( [1] 2 foo "alice" ))
menghasilkan daftar [ 1, 2, 3, "alice" ]
untuk bar
.
(( map1 map2 ))
Penggabungan peta sebagai ekspresi. Urutan peta apa pun dapat digabungkan, diberikan oleh ekspresi dinamis apa pun. Dengan demikian entri akan digabungkan. Entri dengan kunci yang sama ditimpa dari kiri ke kanan.
misalnya:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
hasil
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
Penghitungan nilai otomatis peka konteks.
Dalam atribut 'ukuran' kumpulan sumber daya, ini berarti menghitung berdasarkan total contoh semua pekerjaan yang menyatakan dirinya berada di kumpulan sumber daya saat ini.
misalnya:
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
Dalam hal ini ukuran kumpulan sumber daya akan ditetapkan menjadi '5'.
(( merge ))
Bawa jalur saat ini dari file rintisan yang sedang digabungkan.
misalnya:
foo :
bar :
baz : (( merge ))
Akan mencoba memasukkan foo.bar.baz
dari stub pertama, atau rintisan kedua, dan seterusnya, mengembalikan nilai dari stub terakhir yang menyediakannya.
Jika nilai terkait tidak ditentukan, maka akan menghasilkan nihil. Ini kemudian memiliki semantik yang sama dengan ekspresi referensi; penggabungan nihil adalah templat yang belum terselesaikan. Lihat ||
.
<<: (( merge ))
Penggabungan peta atau daftar dengan konten elemen yang sama ditemukan di beberapa rintisan.
** Perhatian ** Bentuk merge
ini memiliki masalah kompatibilitas. Dalam versi sebelum 1.0.8, ungkapan ini tidak pernah diurai, hanya keberadaan kunci <<:
yang relevan. Oleh karena itu sering kali ada penggunaan <<: (( merge ))
di mana <<: (( merge || nil ))
yang dimaksud. Varian pertama memerlukan konten dalam setidaknya satu stub (seperti biasa untuk operator penggabungan). Sekarang ekspresi ini dievaluasi dengan benar, tetapi ini akan merusak kumpulan template manifes yang ada, yang menggunakan varian pertama, tetapi berarti varian kedua. Oleh karena itu kasus ini ditangani secara eksplisit untuk menggambarkan penggabungan opsional. Jika benar-benar penggabungan yang diperlukan berarti kualifikasi eksplisit tambahan harus dilakukan
Catatan : Daripada menggunakan bidang <<:
insert untuk menempatkan ekspresi gabungan, sekarang Anda juga dapat menggunakan <<<:
, yang memungkinkan penggunaan parser yaml biasa untuk dokumen yaml yang mirip spiff. <<:
disimpan untuk kompatibilitas ke belakang. digunakan ( (( merge required ))
).
Jika kunci gabungan tidak boleh diartikan sebagai kunci biasa dan bukan sebagai arahan penggabungan, kunci tersebut dapat di-escape dengan tanda seru ( !
).
Misalnya, kunci peta <<<!
akan menghasilkan kunci string <<<
dan <<<!!
akan menghasilkan kunci string <<<!
nilai.yml
foo :
a : 1
b : 2
templat.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
menghasilkan:
foo :
a : 1
b : 2
c : 4
nilai.yml
foo :
- 1
- 2
templat.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
menghasilkan:
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
dapat menggabungkan daftar peta dengan bidang kunci. Daftar tersebut ditangani seperti peta dengan nilai bidang kunci sebagai kuncinya. Secara default, name
kunci digunakan. Namun dengan pemilih on
nama kunci arbitrer dapat ditentukan untuk ekspresi penggabungan daftar.
misalnya:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
digabungkan dengan
list :
- key : alice
age : 20
- key : peter
age : 13
hasil
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
Jika tidak ada penyisipan entri baru yang diinginkan (seperti yang diminta oleh ekspresi gabungan penyisipan), tetapi hanya mengesampingkan entri yang ada, satu bidang kunci yang ada dapat diawali dengan key:
untuk menunjukkan nama kunci non-standar, misalnya - key:key: alice
.
<<: (( merge replace ))
Menggantikan konten lengkap suatu elemen dengan konten yang ditemukan di beberapa stub alih-alih melakukan penggabungan mendalam untuk konten yang sudah ada.
nilai.yml
foo :
a : 1
b : 2
templat.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
menghasilkan:
foo :
a : 1
b : 2
nilai.yml
foo :
- 1
- 2
templat.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
menghasilkan:
foo :
- 1
- 2
<<: (( foo ))
Penggabungan peta dan daftar yang ditemukan dalam templat atau rintisan yang sama.
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
hasil:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
Ekspresi ini hanya menambahkan entri baru ke daftar sebenarnya. Itu tidak menggabungkan entri yang ada dengan konten yang dijelaskan oleh ekspresi gabungan.
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
hasil:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
Kasus penggunaan umum untuk ini adalah menggabungkan daftar ip atau rentang statis ke dalam daftar ips. Kemungkinan lainnya adalah dengan menggunakan ekspresi rangkaian tunggal.
<<: (( merge foo ))
Penggabungan peta atau daftar dengan konten elemen arbitrer yang ditemukan di beberapa rintisan (Redirecting merge). Tidak akan ada penggabungan (dalam) lebih lanjut dengan elemen dengan nama yang sama yang ditemukan di beberapa rintisan. (Penggabungan daftar yang mendalam memerlukan peta dengan name
bidang)
Pengarahan ulang penggabungan juga dapat digunakan sebagai nilai bidang langsung. Mereka dapat dikombinasikan dengan penggantian gabungan seperti (( merge replace foo ))
.
nilai.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
templat.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
menghasilkan:
foo :
a : 1
b : 2
c : 4
Cara lain untuk melakukan penggabungan dengan elemen lain di beberapa stub juga bisa dilakukan dengan cara tradisional:
nilai.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
templat.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
Namun dalam skenario ini penggabungan masih melakukan penggabungan mendalam dengan nama elemen aslinya. Oleh karena itu spiff merge template.yml values.yml
menghasilkan:
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
nilai.yml
foo :
- 10
- 20
bar :
- 1
- 2
templat.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
menghasilkan:
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
Jika referensi penggabungan pengalihan diatur ke konstanta none
, penggabungan tidak dilakukan sama sekali. Ekspresi ini selalu menghasilkan nilai nihil.
misalnya: untuk
templat.yml
map :
<< : (( merge none ))
value : notmerged
nilai.yml
map :
value : merged
spiff merge template.yml values.yml
menghasilkan:
map :
value : notmerged
Ini dapat digunakan untuk penggabungan bidang eksplisit menggunakan fungsi stub
untuk mengakses bagian khusus dari stub upstream.
misalnya:
templat.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
nilai.yml
map :
value : bob
spiff merge template.yml values.yml
menghasilkan:
test :
value : alice+bob
Ini juga berfungsi untuk bidang khusus:
templat.yml
map :
value : (( merge none // "alice" "+" stub() ))
nilai.yml
map :
value : bob
spiff merge template.yml values.yml
menghasilkan:
test :
value : alice+bob
(( a || b ))
Menggunakan a, atau b jika a tidak dapat diselesaikan.
misalnya:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
Ini akan mencoba menggabungkan dalam mything.complicated_structure
, atau, jika tidak dapat digabungkan, gunakan default yang ditentukan dalam foo.bar
.
Operator //
juga memeriksa, apakah a
dapat diselesaikan dengan nilai yang valid (tidak sama dengan ~
).
(( 1 + 2 * foo ))
Ekspresi Dynaml dapat digunakan untuk menjalankan penghitungan bilangan bulat aritmatika dan titik mengambang. Operasi yang didukung adalah +
, -
, *
, dan /
. Operator modulo ( %
) hanya mendukung operan bilangan bulat.
misalnya:
nilai.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
menghasilkan 7
untuk bar
. Ini dapat digabungkan dengan penggabungan (penghitungan memiliki prioritas lebih tinggi daripada penggabungan dalam ekspresi dinamis):
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
Hasilnya string 3 times 2 yields 6
.
(( "10.10.10.10" - 11 ))
Selain aritmatika pada bilangan bulat juga dimungkinkan untuk menggunakan penjumlahan dan pengurangan pada alamat ip dan cidrs.
misalnya:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
hasil
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
Pengurangan juga berfungsi pada dua alamat IP atau cidrs untuk menghitung jumlah alamat IP antara dua alamat IP.
misalnya:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
menghasilkan nilai 256. Konstanta alamat IP dapat langsung digunakan dalam ekspresi dinamis. Mereka secara implisit dikonversi menjadi string dan kembali ke alamat IP jika diperlukan oleh suatu operasi.
Perkalian dan pembagian dapat digunakan untuk menangani pergeseran rentang IP pada CIDR. Dengan pembagian suatu jaringan dapat dipartisi. Ukuran jaringan ditingkatkan untuk memungkinkan setidaknya jumlah subnet khusus di bawah CIDR asli. Perkalian kemudian dapat digunakan untuk mendapatkan subnet ke-n berikutnya dengan ukuran yang sama.
misalnya:
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
hasil
subnet : 10.1.2.0/28
next : 10.1.2.32/28
Selain itu ada fungsi yang bekerja pada CIDR IPv4:
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") ))
hasil
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 mendukung operator perbandingan <
, <=
, ==
, !=
, >=
dan >
. Operator perbandingan bekerja pada nilai integer. Pemeriksaan kesetaraan juga dapat dilakukan pada daftar dan peta. Hasilnya selalu berupa nilai boolean. Untuk meniadakan suatu kondisi, operator unary bukan ( !
) dapat digunakan.
Selain itu ada operator kondisional ternary ?:
, yang dapat digunakan untuk mengevaluasi ekspresi bergantung pada suatu kondisi. Operan pertama digunakan sebagai syarat. Ekspresi dievaluasi ke operan kedua, jika kondisinya benar, dan ke operan ketiga, jika kondisinya benar.
misalnya:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
menghasilkan nilai bob
untuk properti name
.
Suatu ekspresi dianggap false
jika dievaluasi
false
Jika tidak maka dianggap true
Komentar
Penggunaan simbol :
mungkin bertabrakan dengan sintaksis yaml, jika ekspresi lengkapnya bukan nilai string yang dikutip.
Operator -or
dan -and
dapat digunakan untuk menggabungkan operator perbandingan untuk menyusun kondisi yang lebih kompleks.
Komentar:
Simbol operator yang lebih tradisional ||
(dan &&
) tidak dapat digunakan di sini, karena operator ||
sudah ada di dinamis dengan semantik berbeda, yang tidak berlaku untuk operasi logis. Ekspresi false || true
bernilai false
, karena menghasilkan operan pertama, jika ditentukan, berapa pun nilainya. Agar serasi mungkin, hal ini tidak dapat diubah dan simbol kosong or
dan and
tidak dapat digunakan, karena hal ini akan membatalkan rangkaian referensi dengan nama tersebut.
(( 5 -or 6 ))
Jika kedua sisi operator -or
atau -and
bernilai bilangan bulat, operasi bit-bijaksana dijalankan dan hasilnya kembali berupa bilangan bulat. Oleh karena itu ekspresi 5 -or 6
bernilai 7
.
Dynaml mendukung serangkaian fungsi yang telah ditentukan sebelumnya. Suatu fungsi umumnya disebut suka
result : (( functionname(arg, arg, ...) ))
Fungsi tambahan dapat didefinisikan sebagai bagian dari dokumen yaml menggunakan ekspresi lambda. Nama fungsi kemudian berupa ekspresi yang dikelompokkan atau jalur ke node yang menghosting ekspresi lambda.
(( format( "%s %d", alice, 25) ))
Format string berdasarkan argumen yang diberikan oleh ekspresi dinamis. Ada jenis kedua dari fungsi ini: error
memformat pesan kesalahan dan menyetel evaluasi menjadi gagal.
(( join( ", ", list) ))
Gabungkan entri daftar atau arahkan nilai ke nilai string tunggal menggunakan string pemisah tertentu. Argumen untuk digabungkan dapat berupa ekspresi dinamis yang mengevaluasi daftar, yang nilainya lagi-lagi berupa string atau bilangan bulat, atau nilai string atau bilangan bulat.
misalnya:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
menghasilkan nilai string bob, foo, bar, alice, 10
untuk join
.
(( split( ",", string) ))
Pisahkan string untuk pemisah khusus. Hasilnya adalah sebuah daftar. Alih-alih string pemisah, nilai integer mungkin diberikan, yang membagi string yang diberikan menjadi daftar string yang panjangnya terbatas. Panjangnya dihitung dalam rune, bukan byte.
misalnya:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
hasil:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
Argumen opsional ke-3 mungkin ditentukan. Ini membatasi jumlah entri daftar yang dikembalikan. Nilai -1 menyebabkan panjang daftar tidak terbatas.
Jika ekspresi reguler harus digunakan sebagai string pemisah, fungsi split_match
dapat digunakan.
(( trim(string) ))
Pangkas string atau semua elemen daftar string. Ada argumen string kedua opsional. Ini dapat digunakan untuk menentukan sekumpulan karakter yang akan dipotong. Kumpulan potongan default terdiri dari spasi dan karakter tab.
misalnya:
list : (( trim(split("," "alice, bob")) ))
hasil:
list :
- alice
- bob
(( element(list, index) ))
Mengembalikan elemen daftar khusus yang diberikan oleh indeksnya.
misalnya:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
hasil:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
Mengembalikan bidang peta khusus yang diberikan oleh kuncinya.
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
hasil:
map :
alice : 24
bob : 25
elem : 25
Fungsi ini juga mampu menangani tombol yang mengandung titik (.).
(( compact(list) ))
Filter daftar dengan menghilangkan entri kosong.
misalnya:
list : (( compact(trim(split("," "alice, , bob"))) ))
hasil:
list :
- alice
- bob
(( uniq(list) ))
Uniq menyediakan daftar tanpa duplikat.
misalnya:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
hasil untuk bidang uniq
:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
Memeriksa apakah daftar berisi nilai khusus. Nilai juga bisa berupa daftar atau peta.
misalnya:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
hasil:
list :
- foo
- bar
- foobar
contains : true
Fungsi contains
juga berfungsi pada string untuk mencari sub string atau peta untuk mencari kunci. Dalam kasus tersebut, elemennya harus berupa string.
misalnya:
contains : (( contains("foobar", "bar") ))
menghasilkan true
.
(( basename(path) ))
Fungsi basename
mengembalikan nama elemen terakhir dari suatu jalur. Argumennya bisa berupa nama jalur biasa atau URL.
misalnya:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
hasil:
pathbase : bob
urlbase : bob
(( dirname(path) ))
Fungsi dirname
mengembalikan direktori induk dari suatu jalur. Argumennya bisa berupa nama jalur biasa atau URL.
misalnya:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
hasil:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
Fungsi ini mem-parsing URL dan menghasilkan peta dengan semua elemen URL. Bidang port
, userinfo
, dan password
bersifat opsional.
misalnya:
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
hasil:
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") ))
Periksa apakah daftar berisi nilai khusus dan mengembalikan indeks pertandingan pertama. Nilai mungkin juga daftar atau peta. Jika tidak ada entri yang dapat ditemukan -1
dikembalikan.
misalnya:
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
Hasil:
list :
- foo
- bar
- foobar
index : 2
index
fungsi juga berfungsi pada string untuk mencari sub -string.
misalnya:
index : (( index("foobar", "bar") ))
menghasilkan 3
.
(( lastindex(list, "foobar") ))
Fungsi lastindex
berfungsi seperti index
tetapi indeks kejadian terakhir dikembalikan.
sort
fungsi dapat digunakan untuk mengurutkan daftar integer atau string. Operasi sortir stabil.
misalnya:
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
hasil untuk sorted
- alice
- bob
- foobar
Jika jenis lain harus diurutkan, terutama jenis kompleks seperti daftar atau peta, atau aturan perbandingan yang berbeda diperlukan, fungsi perbandingan dapat ditentukan sebagai argumen kedua opsional. Fungsi perbandingan harus berupa ekspresi lambda yang mengambil dua argumen. Jenis hasil harus integer
atau bool
yang menunjukkan apakah a kurang dari b . Jika bilangan bulat dikembalikan seharusnya
misalnya:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
hasil untuk sorted
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
Ganti semua kejadian sub string dalam string dengan string pengganti. Dengan argumen integer keempat opsional, jumlah substitusi dapat dibatasi (-1 rata-rata tidak terbatas).
misalnya:
string : (( replace("foobar", "o", "u") ))
menghasilkan fuubar
.
Jika ekspresi reguler harus digunakan sebagai string pencarian, fungsi replace_match
dapat digunakan. Di sini string pencarian dievaluasi sebagai ekspresi reguler. Itu mungkin konatain sub -ekspresi. Kecocokan ini dapat digunakan dalam string pengganti
misalnya:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
menghasilkan fbooar
.
Argumen penggantian mungkin juga fungsi lambda. Dalam hal ini, untuk setiap kecocokan fungsi dipanggil untuk menentukan nilai penggantian. Argumen input tunggal adalah daftar kecocokan sub ekspresi aktual.
misalnya:
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
menghasilkan fOObar-barfoo
.
(( substr(string, 1, 2) ))
Ekstrak string rintisan dari string, mulai dari indeks awal yang diberikan hingga indeks akhir opsional (eksklusif). Jika tidak ada indeks akhir yang diberikan sub struvt hingga akhir string diekstraksi. Kedua indeks mungkin negatif. Dalam hal ini mereka diambil dari ujung string.
misalnya:
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
mengevaluasi ke
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
Mengembalikan kecocokan ekspresi reguler untuk nilai string yang diberikan. Pertandingan adalah daftar nilai yang cocok untuk sub ekspresi yang terkandung dalam ekspresi reguler. Indeks 0 mengacu pada kecocokan ekspresi reguler lengkap. Jika nilai string tidak cocok dengan daftar kosong dikembalikan.
misalnya:
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
Hasil:
matches :
- foobar
- foo
- bar
Argumen ketiga dari tipe integer dapat diberikan untuk meminta multi kecocokan maksimal N pengulangan. Jika nilainya negatif, semua repetisi dilaporkan. Hasilnya adalah daftar semua kecocokan, masing -masing dalam format yang dijelaskan di atas.
(( keys(map) ))
Tentukan daftar kunci yang diurutkan yang digunakan dalam peta.
misalnya:
map :
alice : 25
bob : 25
keys : (( keys(map) ))
Hasil:
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
Tentukan panjang daftar, peta atau nilai string.
misalnya:
list :
- alice
- bob
length : (( length(list) ))
Hasil:
list :
- alice
- bob
length : 2
(( base64(string) ))
Fungsi base64
menghasilkan pengkodean base64 dari string yang diberikan. base64_decode
mendekode string yang dikodekan base64.
misalnya:
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
mengevaluasi ke
base54 : dGVzdA==
test : test
Argumen kedua opsional dapat digunakan untuk menentukan panjang garis maksimum. Dalam hal ini hasilnya akan menjadi string multi-line.
(( hash(string) ))
Fungsi hash
menghasilkan beberapa jenis hash untuk string yang diberikan. Secara default karena hash sha256
dihasilkan. Argumen kedua opsional menentukan jenis hash. Jenis yang mungkin adalah md4
, md5
, sha1
, sha224
, sha256
, sha384
, sha2512
, sha512/224
atau sha512/256
.
Hash md5
masih dapat dihasilkan oleh Finctio md5(string)
yang sudah usang.
misalnya:
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") ))
mengevaluasi ke
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) ))
Fungsi bcrypt
menghasilkan hash kata sandi bcrypt untuk string yang diberikan menggunakan faktor biaya yang ditentukan (default ke 10, jika hilang).
misalnya:
hash : (( bcrypt("password", 10) ))
mengevaluasi ke
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
Fungsi bcrypt_check
memvalidasi kata sandi terhadap hash bcrypt yang diberikan.
misalnya:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
mengevaluasi ke
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
Fungsi md5crypt
menghasilkan hash kata sandi terenkripsi Apache MD5 untuk string yang diberikan.
misalnya:
hash : (( md5crypt("password") ))
mengevaluasi ke
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
Fungsi md5crypt_check
memvalidasi kata sandi terhadap hash terenkripsi Apache MD5 yang diberikan.
misalnya:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
mengevaluasi ke
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
Fungsi ini dapat digunakan untuk menyimpan rahasia terenkripsi dalam file Spiff Yaml. Hasil yang diproses kemudian akan berisi nilai yang didekripsi. Semua jenis simpul dapat dienkripsi dan didekripsi, termasuk peta dan daftar lengkap.
Kata sandi untuk dekripsi dapat diberikan sebagai argumen kedua, atau (cara yang disukai) dapat ditentukan oleh variabel lingkungan SPIFF_ENCRYPTION_KEY
.
Argumen terakhir opsional dapat memilih metode enkripsi. Satu -satunya metode yang didukung sejauh ini adalah 3DES
. Metode lain dapat ditambahkan untuk versi spiff khusus dengan menggunakan pendaftaran metode enkripsi yang ditawarkan oleh Perpustakaan Spiff.
Nilai dapat dienkripsi dengan menggunakan fungsi encrypt("secret")
.
misalnya:
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) ))
dievaluasi ke sesuatu seperti
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) ))
Fungsi rand
menghasilkan nilai acak. Argumen pertama memutuskan nilai apa yang diminta. Tanpa argumen itu menghasilkan angka acak positif dalam kisaran int64
.
tipe argumen | hasil |
---|---|
ke dalam | Nilai integer dalam kisaran [0, n ) untuk n positif dan ( n , 0] untuk negatif n |
bodoh | Nilai Boolean |
rangkaian | Satu string rune, di mana rune berada dalam rentang karakter yang diberikan, kombinasi kelas karakter atau rentang karakter yang dapat digunakan untuk RegExp dapat digunakan. Jika argumen panjang tambahan ditentukan, string yang dihasilkan akan memiliki panjang yang diberikan. |
misalnya:
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) ))
mengevaluasi ke
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
type
fungsi menghasilkan string yang menunjukkan jenis ekspresi yang diberikan.
misalnya:
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(~~) ))
mengevaluasi jenis ke
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
Fungsi defined
memeriksa apakah suatu ekspresi dapat berhasil dievaluasi. Ini menghasilkan nilai boolean true
, jika ekspresi dapat dievaluasi, dan false
sebaliknya.
misalnya:
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
mengevaluasi ke
zero : 0
div_ok : false
zero_def : true
null_def : false
Fungsi ini dapat digunakan dalam kombinasi operator bersyarat untuk mengevaluasi ekspresi tergantung pada resolvabilitas ekspresi lain.
(( valid(foobar) ))
Fungsi valid
memeriksa apakah suatu ekspresi dapat berhasil dievaluasi dan dievaluasi dengan nilai yang ditentukan tidak sama dengan nil
. Ini menghasilkan nilai boolean true
, jika ekspresi dapat dievaluasi, dan false
sebaliknya.
misalnya:
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 ) ))
mengevaluasi ke
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) ))
Fungsi tersebut require
hasil kesalahan jika argumen yang diberikan tidak ditentukan atau nil
, jika tidak ia menghasilkan nilai yang diberikan.
misalnya:
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
mengevaluasi ke
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
Fungsi stub
menghasilkan nilai bidang khusus yang ditemukan di rintisan hulu pertama yang mendefinisikannya.
misalnya:
template.yml
value : (( stub(foo.bar) ))
bergabung dengan rintisan
stub.yml
foo :
bar : foobar
mengevaluasi ke
value : foobar
Argumen yang diteruskan ke fungsi ini harus berupa referensi literal atau ekspresi yang mengevaluasi baik string yang menunjukkan referensi atau daftar string yang menunjukkan daftar elemen jalur untuk referensi. Jika tidak ada argumen atau tidak terdefinisi ( ~~
) diberikan, jalur bidang yang sebenarnya digunakan.
Harap dicatat, bahwa satu -satunya referensi yang diberikan tidak akan dievaluasi sebagai ekspresi, jika nilainya harus digunakan, itu harus diubah menjadi ekspresi, misalnya dengan menunjukkan (ref)
atau [] ref
untuk ekspresi daftar.
Atau operasi merge
dapat digunakan, misalnya merge foo.bar
. Perbedaannya adalah bahwa stub
tidak bergabung, oleh karena itu bidang masih akan digabungkan (dengan jalur asli dalam dokumen).
(( tagdef("tag", value) ))
Fungsi tagdef
dapat digunakan untuk mendefinisikan tag dinamis (lihat tag). Berbeda dengan penanda tag fungsi ini memungkinkan untuk menentukan nama tag dan nilai yang dimaksudkan dengan ekspresi. Oleh karena itu, dapat digunakan dalam menyusun elemen seperti map
atau sum
untuk membuat tag dinamis dengan nilai yang dihitung.
Argumen ketiga opsional dapat digunakan untuk menentukan ruang lingkup yang dimaksud ( local
atau global
). Secara default tag lokal dibuat. Tag lokal hanya terlihat pada tingkat pemrosesan yang sebenarnya (templat atau sub), sedangkan tag global, setelah didefinisikan, dapat digunakan dalam semua tingkat pemrosesan lebih lanjut (rintisan atau templat).
Atau nama tag dapat diawali dengan start ( *
) untuk mendeklarasikan tag global.
Nilai tag yang ditentukan akan digunakan sebagai hasil untuk fungsi.
misalnya:
template.yml
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
mengevaluasi ke
value : 25
alice : 25
(( eval(foo "." bar ) ))
Evaluasi hasil evaluasi dari ekspresi string lagi sebagai ekspresi dinaml. Ini dapat, misalnya, dapat digunakan untuk mewujudkan ketidakpastian.
misalnya: ekspresi masuk
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
menghitung jalur ke bidang, yang kemudian dievaluasi lagi untuk menghasilkan nilai bidang yang disusun ini:
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
Baca nilai variabel lingkungan yang namanya diberikan sebagai ekspresi dinaml. Jika variabel lingkungan tidak ditetapkan, evaluasi gagal.
Dalam rasa kedua fungsi env
menerima beberapa argumen dan/atau daftar argumen, yang bergabung dalam satu daftar. Setiap entri dalam daftar ini digunakan sebagai nama variabel lingkungan dan hasil fungsi adalah peta variabel yang diberikan sebagai elemen yaml. Dengan ini variabel lingkungan yang tidak ada dihilangkan.
(( parse(yamlorjson) ))
Parse string YAML atau JSON dan kembalikan konten sebagai nilai YAML. Oleh karena itu dapat digunakan untuk evaluasi Dynaml lebih lanjut.
misalnya:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
menghasilkan nilai 25
untuk result
lapangan.
Fungsi parse
mendukung argumen kedua opsional, mode parse . Di sini mode yang sama dimungkinkan seperti untuk fungsi baca. Mode parsing default adalah import
, konten hanya diuraikan dan tidak ada evaluasi lebih lanjut selama langkah ini.
(( asjson(expr) ))
Fungsi ini mengubah nilai YAML yang diberikan oleh argumennya menjadi string JSON . Fungsi yang sesuai asyaml
menghasilkan nilai YAML sebagai string dokumen YAML .
misalnya:
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
menyelesaikan untuk
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
Fungsi ini menjalankan ekspresi dan menghasilkan beberapa peta info evaluasi. Itu selalu berhasil, bahkan jika ekspresi gagal. Peta ini mencakup bidang -bidang berikut:
nama | jenis | arti |
---|---|---|
valid | bodoh | Ekspresi valid |
error | rangkaian | teks pesan kesalahan evaluasi |
value | setiap | nilai ekspresi, jika evaluasi berhasil |
misalnya:
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
menyelesaikan untuk
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
Hasilkan daftar IP statis untuk suatu pekerjaan.
misalnya:
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
Ini akan membuat 3 IP dari subnet mynetwork
, dan mengembalikan dua entri, karena hanya ada dua contoh. Kedua entri akan menjadi offset ke -0 dan ke -3 dari rentang IP statis yang ditentukan oleh jaringan.
Misalnya, mengingat file bye.yml :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
dan file 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
kembali
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
.
Jika bye.yml malah
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
alih -alih kembali
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
juga menerima argumen daftar, selama semua elemen yang terkandung secara transitival adalah daftar lagi atau nilai integer. Ini memungkinkan untuk menyingkat daftar IPS sebagai berikut:
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
Sementara fungsi static_ips untuk alasan historis bergantung pada struktur manifes bosh dan hanya bekerja di lokasi khusus dalam manifes, fungsi Ipset menawarkan perhitungan serupa murni berdasarkan argumennya. Jadi, rentang IP yang tersedia dan jumlah IP yang diperlukan dilewatkan sebagai argumen.
Argumen pertama (rentang) dapat berupa kisaran tunggal sebagai string sederhana atau daftar string. Setiap string mungkin
Argumen kedua menentukan jumlah alamat IP yang diminta dalam set hasil.
Argumen tambahan menentukan indeks IPS untuk memilih (mulai dari 0) dalam rentang yang diberikan. Di sini sekali lagi daftar indeks dapat digunakan.
misalnya:
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
Menyelesaikan ipset ke [ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
.
Jika tidak ada indeks IP yang ditentukan (hanya dua argumen), IP dipilih mulai dari awal kisaran pertama hingga akhir kisaran terakhir yang diberikan, tanpa tidak langsung.
(( list_to_map(list, "key") ))
Daftar entri peta dengan nama eksplisit/bidang kunci akan dipetakan ke peta dengan tombol khusus. Secara default name
bidang kunci digunakan, yang dapat diubah oleh argumen kedua opsional. Bidang kunci yang dilambangkan secara eksplisit dalam daftar juga akan diperhitungkan.
misalnya:
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
akan dipetakan ke
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
Dalam kombinasi dengan templat dan ekspresi lambda ini dapat digunakan untuk menghasilkan peta dengan nilai -nilai kunci yang dinamai secara sewenang -wenang, meskipun ekspresi dinaml tidak diperbolehkan untuk nilai -nilai kunci.
(( makemap(fieldlist) ))
Dalam rasa ini makemap
membuat peta dengan entri yang dijelaskan oleh daftar bidang yang diberikan. Daftar ini diharapkan berisi peta dengan key
dan value
entri, menjelaskan entri peta khusus.
misalnya:
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
hasil
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
Jika nilai kunci adalah boolean atau bilangan bulat itu akan dipetakan ke string.
(( makemap(key, value) ))
Dalam rasa ini makemap
membuat peta dengan entri yang dijelaskan oleh pasangan argumen yang diberikan. Argumen mungkin merupakan urutan pasangan kunci/nilai (diberikan oleh argumen terpisah).
misalnya:
map : (( makemap("peter", 23, "paul", 22) ))
hasil
map :
paul : 22
peter : 23
Berbeda dengan rasa makemap
sebelumnya, yang satu ini juga bisa ditangani oleh literal peta.
(( merge(map1, map2) ))
Selain merge
kata kunci ada juga fungsi yang disebut merge
(harus selalu diikuti oleh braket pembuka). Ini dapat digunakan untuk menggabungkan peta pimpinan yang diambil dari dokumen aktual yang analog dengan proses penggabungan rintisan. Jika peta ditentukan oleh ekspresi referensi, mereka tidak dapat berisi ekspresi dinaml , karena mereka selalu dievaluasi dalam konteks dokumen aktual sebelum mengevaluasi argumen.
misalnya:
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
menyelesaikan result
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
Sebagai alternatif, templat Peta dapat dilewati (tanpa operator evaluasi!). Dalam hal ini ekspresi Dynaml dari templat dievaluasi sambil menggabungkan dokumen yang diberikan untuk panggilan rutin gabungan spiff .
misalnya:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
menyelesaikan result
result :
alice : 26
bob : 26
Peta mungkin juga diberikan oleh ekspresi peta. Di sini dimungkinkan untuk menentukan ekspresi Dynaml menggunakan sintaks biasa:
misalnya:
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
menyelesaikan result
result :
alice : 26
bob : 100
Alih -alih beberapa argumen, satu argumen daftar tunggal dapat diberikan. Daftar harus berisi peta yang akan digabungkan.
Penggabungan bersarang memiliki akses ke semua binding luar. Referensi relatif pertama kali dicari dalam dokumen aktual. Jika mereka tidak ditemukan di sana, semua binding luar digunakan untuk mencari referensi, dari binding bagian dalam ke luar. Selain itu konteks ( __ctx
) menawarkan lapangan OUTER
, yang merupakan daftar semua dokumen luar dari penggabungan bersarang, yang dapat digunakan untuk mencari referensi absolut.
misalnya:
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) ))
Penyelesaian merged
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
Fungsi intersect
memotong beberapa daftar. Daftar dapat berisi entri jenis apa pun.
misalnya:
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) ))
Menyelesaikan intersect
menjadi
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
Fungsi reverse
membalikkan urutan daftar. Daftar ini dapat berisi entri jenis apa pun.
misalnya:
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
menyelesaikan reverse
menjadi
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
Fungsi validate
memvalidasi ekspresi menggunakan satu set validator. Argumen pertama adalah nilai untuk divalidasi dan semua argumen lainnya adalah validator yang harus berhasil menerima nilainya. Jika setidaknya satu validator gagal, pesan kesalahan yang sesuai dihasilkan yang menjelaskan alasan gagal.
Validator dilambangkan dengan string atau daftar yang berisi jenis validator sebagai string dan argumennya. Validator dapat dinegasikan dengan preceeding !
atas namanya.
Validator berikut tersedia:
Jenis | Argumen | Arti |
---|---|---|
empty | tidak ada | daftar kosong, peta atau string |
dnsdomain | tidak ada | Nama domain DNS |
wildcarddnsdomain | tidak ada | Nama domain Wildcard DNS |
dnslabel | tidak ada | Label DNS |
dnsname | tidak ada | domain DNS atau domain wildcard |
ip | tidak ada | alamat IP |
cidr | tidak ada | CIDR |
publickey | tidak ada | kunci publik dalam format PEM |
privatekey | tidak ada | kunci pribadi dalam format PEM |
certificate | tidak ada | Sertifikat dalam format PEM |
ca | tidak ada | Sertifikat untuk ca |
semver | Daftar kendala opsional | Validasi versi SEMVER melawan kendala |
type | Daftar Kunci Jenis yang Diterima | setidaknya satu jenis kunci harus cocok |
valueset | daftar argumen dengan nilai | nilai yang mungkin |
value atau = | nilai | periksa nilai khusus |
gt atau > | nilai | lebih besar dari (angka/string) |
lt atau < | nilai | kurang dari (angka/string) |
ge atau >= | nilai | lebih besar atau sama dengan (angka/string) |
le atau <= | nilai | kurang atau sama dengan (angka/string) |
match atau ~= | ekspresi reguler | nilai string mencocokkan ekspresi reguler |
list | Daftar Validator Entri Opsional | adalah daftar dan entri cocok dengan validator yang diberikan |
map | [[<Key Validator>,] <Entry Validator>] | adalah peta dan kunci dan entri cocok dengan validator yang diberikan |
mapfield | <Name Field> [, <Validator>] | Entri yang Diperlukan di Peta |
optionalfield | <Name Field> [, <Validator>] | entri opsional di peta |
and | Daftar validator | Semua validator harus berhasil |
or | Daftar validator | setidaknya satu validator harus berhasil |
not atau ! | validator | Meninggalkan argumen validator |
Jika validasi berhasil, nilainya dikembalikan.
misalnya:
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
mengevaluasi ke
dnstarget : 192.168.42.42
Jika validasi gagal, kesalahan yang menjelaskan alasan kegagalan dihasilkan.
misalnya:
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
menghasilkan kesalahan berikut:
*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])?)*')])
Validator mungkin juga ekspresi Lambda yang mengambil setidaknya satu argumen dan mengembalikan nilai boolean. Dengan cara ini dimungkinkan untuk memberikan validator sendiri sebagai bagian dari dokumen YAML.
misalnya:
val : (( validate( 0, |x|-> x > 1 ) ))
Jika lebih dari satu parameter dinyatakan, argumen tambahan harus ditentukan sebagai argumen validator. Argumen pertama selalu nilainya untuk diperiksa.
misalnya:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
Fungsi lambda dapat mengembalikan daftar dengan elemen 1, 2 atau 3, juga. Ini dapat digunakan untuk menyediakan pesan yang sesuai.
Indeks | Arti |
---|---|
0 | Indeks pertama selalu adalah hasil pertandingan, itu harus dievaluasi sebagai boolean |
1 | Jika dua elemen diberikan, indeks kedua adalah pesan yang menggambarkan hasil aktual |
2 | di sini indeks 1 mengurangi pesan keberhasilan dan 2 pesan kegagalan |
misalnya:
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
Hanya untuk menyebutkan, spesifikasi validator mungkin diberikan sebaris seperti yang ditunjukkan pada contoh di atas, tetapi juga sebagai ekspresi referensi. not
, and
dan or
validator menerima spesifikasi validator yang sangat bersarang.
misalnya:
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
check
fungsi dapat digunakan untuk mencocokkan struktur YAML dengan pemeriksa nilai berbasis YAML. Dengan ini deskripsi cek yang sama yang sudah dijelaskan untuk divalidasi dapat digunakan. Hasil panggilan adalah nilai boolean yang menunjukkan hasil pertandingan. Tidak gagal jika cek gagal.
(( error("message") ))
error
fungsi dapat digunakan untuk menyebabkan kegagalan evaluasi eksplisit dengan pesan khusus.
Ini dapat digunakan, misalnya, untuk mengurangi kesalahan pemrosesan yang kompleks ke pesan yang bermakna dengan menambahkan fungsi kesalahan sebagai default untuk ekspresi Comples yang berpotensi gagal.
misalnya:
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
Skenario lain dapat menghilangkan pesan deskriptif untuk bidang yang diperlukan dengan menggunakan nilai ekspresi kesalahan sebagai (default) untuk bidang yang dimaksudkan untuk didefinisikan dalam rintisan hulu.
Dynaml mendukung berbagai fungsi matematika:
Bilangan bulat yang kembali: ceil
, floor
, round
dan roundtoeven
Mengembalikan pelampung atau bilangan bulat: abs
Mengembalikan pelampung: sin
, cos
, sinh
, cosh
, asin
, acos
, asinh
, acosh
, sqrt
, exp
, log
, log10
,
Dynaml mendukung berbagai konversi jenis antara integer
, float
, bool
dan nilai string
dengan fungsi yang sesuai.
misalnya:
value : (( integer("5") ))
mengubah string ke nilai integer.
Mengonversi integer ke string menerima argumen integer tambahan opsional untuk menentukan basis untuk konversi, misalnya string(55,2)
akan menghasilkan "110111"
. Basis default adalah 10. Basis harus antara 2 dan 36.
Spiff mendukung akses ke konten di luar templat dan sub file. Dimungkinkan untuk membaca file, menjalankan perintah dan pipa. Semua fungsi itu ada dalam dua rasa.
sync
digunakan, yang dimaksudkan untuk menyinkronkan pemrosesan template dengan keadaan dedikasi (disediakan oleh konten eksternal). Di sini operasi caching tidak akan berguna, oleh karena itu ada rasa kedua yang tidak terkena. Setiap fungsi tersedia dengan akhiran _uncached
(misalnya read_uncached()
) (( read("file.yml") ))
Baca file dan kembalikan kontennya. Ada dukungan untuk tiga jenis konten: file yaml
, file text
dan file binary
. Membaca dalam mode biner akan menghasilkan string multi-line base64 yang dikodekan.
Jika sufiks file adalah .yml
, .yaml
atau .json
, secara default jenis YAML digunakan. Jika file harus dibaca sebagai text
, jenis ini harus ditentukan secara eksplisit. Dalam semua kasus lain defaultnya adalah text
, oleh karena itu membaca file biner (misalnya arsip) segera memerlukan penentu mode binary
.
Parameter kedua opsional dapat digunakan untuk secara eksplisit menentukan jenis pengembalian yang diinginkan: yaml
atau text
. Untuk dokumen YAML , beberapa jenis tambahan didukung: multiyaml
, template
, templates
, import
dan importmulti
.
Dokumen YAML akan diuraikan dan pohon dikembalikan. Elemen -elemen pohon dapat diakses dengan ekspresi dinaml biasa.
Selain itu file YAML dapat kembali berisi ekspresi Dynaml. Semua ekspresi Dynaml yang disertakan akan dievaluasi dalam konteks ekspresi membaca. Ini berarti bahwa file yang sama termasuk di tempat yang berbeda dalam dokumen YAML dapat menghasilkan sub pohon yang berbeda, tergantung pada ekspresi Dynaml yang digunakan.
Jika memungkinkan untuk membaca YAML multi-dokumen, juga. Jika tipe multiyaml
diberikan, simpul daftar dengan node root dokumen YAML dikembalikan.
Dokumen YAML atau JSON juga dapat dibaca sebagai templat dengan menentukan Tipe template
. Di sini hasilnya akan menjadi nilai templat, yang dapat digunakan seperti templat inline biasa. Jika templates
ditentukan, multi-dokumen dipetakan ke daftar templat.
Jika jenis baca diatur untuk import
, konten file dibaca sebagai dokumen YAML dan simpul root digunakan untuk mengganti ekspresi. Potensi ekspresi dinaml yang terkandung dalam dokumen tidak akan dievaluasi dengan ikatan ekspresi yang sebenarnya bersama dengan panggilan baca, tetapi karena itu akan menjadi bagian dari file asli. Oleh karena itu mode ini hanya dapat digunakan, jika tidak ada pemrosesan lebih lanjut dari hasil yang dibaca atau nilai yang disampaikan tidak diproses.
Ini dapat digunakan bersama dengan referensi rantai (untuk examle (( read(...).selection ))
) untuk menghapus fragmen khusus dari dokumen yang diimpor. Kemudian, evaluasi akan dilakukan untuk bagian yang dipilih saja. Ekspresi dan referensi di bagian lain tidak dievaluasi dan sama sekali dan tidak dapat menyebabkan kesalahan.
misalnya:
template.yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
impor.yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
Tidak akan gagal, karena bagian second
tidak pernah dievaluasi.
Mode ini harus diambil dengan hati -hati, karena sering mengarah pada hasil yang tidak terduga.
Jenis baca importmulti
dapat digunakan untuk mengimpor file YAML multi-dokumen sebagai daftar node.
Dokumen teks akan dikembalikan sebagai string tunggal.
Dimungkinkan untuk membaca dokumen biner juga. Konten tidak dapat digunakan sebagai string (atau dokumen YAML), secara langsung. Oleh karena itu binary
Mode Baca harus ditentukan. Konten dikembalikan sebagai nilai string multi-line base64 yang dikodekan.
(( exec("command", arg1, arg2) ))
Jalankan perintah. Argumen dapat berupa ekspresi dinaml termasuk ekspresi referensi yang dievaluasi ke daftar atau peta. Daftar atau peta disahkan sebagai argumen tunggal yang berisi dokumen YAML dengan fragmen yang diberikan.
Hasilnya ditentukan dengan parsing output standar dari perintah. Ini mungkin dokumen YAML atau string multi-line tunggal atau nilai integer. Dokumen YAML harus dimulai dengan awalan dokumen ---
. Jika perintah gagal, ekspresi ditangani sebagai tidak terdefinisi.
misalnya
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
hasil
arg :
- a
- b
list :
- a
- b
string : a
Atau exec
dapat dipanggil dengan argumen daftar tunggal sepenuhnya menggambarkan baris perintah.
Perintah yang sama akan dieksekusi sekali, hanya, bahkan jika digunakan dalam beberapa ekspresi.
(( pipe(data, "command", arg1, arg2) ))
Jalankan perintah dan beri makan input standarnya dengan data khusus. Argumen perintah harus berupa string. Argumen untuk perintah dapat berupa ekspresi dinaml termasuk ekspresi referensi yang dievaluasi ke daftar atau peta. Daftar atau peta disahkan sebagai argumen tunggal yang berisi dokumen YAML dengan fragmen yang diberikan.
Aliran input dihasilkan dari data yang diberikan. Jika ini adalah tipe sederhana representasi stringnya digunakan. Kalau tidak, dokumen YAML dihasilkan dari data input. Hasilnya ditentukan dengan parsing output standar dari perintah. Ini mungkin dokumen YAML atau string multi-line tunggal atau nilai integer. Dokumen YAML harus dimulai dengan awalan dokumen ---
. Jika perintah gagal, ekspresi ditangani sebagai tidak terdefinisi.
misalnya
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
hasil
arg :
- a
- b
list :
- z
- b
Atau pipe
dapat dipanggil dengan data dan argumen daftar sepenuhnya menggambarkan baris perintah.
Perintah yang sama akan dieksekusi sekali, hanya, bahkan jika digunakan dalam beberapa ekspresi.
(( write("file.yml", data) ))
Tulis file dan kembalikan kontennya. Jika hasilnya dapat diuraikan sebagai dokumen YAML, dokumen dikembalikan. Argumen ke -3 opsional dapat digunakan untuk lulus opsi tulis. Argumen opsi mungkin integer yang menunjukkan izin file (default adalah 0644
) atau string yang dipisahkan koma dengan opsi. Opsi yang didukung adalah
binary
: Data adalah basis64 diterjemahkan sebelum menulis0
terkemuka menunjukkan nilai oktal. (( tempfile("file.yml", data) ))
Tulis file sementara AA dan kembalikan nama jalurnya. Argumen ke -3 opsional dapat digunakan untuk lulus opsi tulis. Itu pada dasarnya berperilaku seperti write
Perhatian : File sementara hanya ada selama pemrosesan gabungan. Itu akan dihapus sesudahnya.
Dapat digunakan, misalnya, untuk memberikan argumen file sementara untuk fungsi exec
.
(( lookup_file("file.yml", list) ))
Cari file adalah daftar direktori. Hasilnya adalah daftar file yang ada. Dengan lookup_dir
adalah mungkin untuk mencari direktori, sebagai gantinya.
Jika tidak ada file yang ada dapat ditemukan, daftar kosong dikembalikan.
Dimungkinkan untuk lulus beberapa argumen daftar atau string untuk menyusun jalur pencarian.
(( mkdir("dir", 0755) ))
Buat direktori dan semua direktori perantara jika belum ada.
Bagian izin adalah opsional (default 0755). Jalur direktori mungkin diberikan dengan nilai seperti atau sebagai daftar komponen jalur.
(( list_files(".") ))
Daftar file dalam direktori. Hasilnya adalah daftar file yang ada. Dengan list_dirs
dimungkinkan untuk membuat daftar direktori, sebagai gantinya.
(( archive(files, "tar") ))
Buat arsip tipe yang diberikan (default adalah tar
) yang berisi file yang terdaftar. Hasilnya adalah arsip yang dikodekan base64.
Jenis arsip yang didukung adalah tar
dan targz
.
files
mungkin daftar atau peta entri file. Dalam hal peta, kunci peta digunakan sebagai default untuk jalur file. Entri file adalah peta dengan bidang berikut:
bidang | jenis | arti |
---|---|---|
path | rangkaian | opsional untuk peta, jalur file di arsip, default oleh kunci peta |
mode | string int atau int | mode file atau opsi tulis. Ini pada dasarnya berperilaku seperti argumen opsi untuk write . |
data | setiap | Konten file, YAML akan dikeluarkan sebagai dokumen YAML. Jika mode menunjukkan mode biner, nilai string akan didekodekan. |
base64 | rangkaian | base64 data biner yang dikodekan |
misalnya:
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"))) ))
Hasil:
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 mendukung penanganan nama versi semantik. Ini mendukung semua fungsionalitas dari Masterminds SEMVER Paket menerima versi dengan atau tanpa ver v
.
(( semver("v1.2-beta.1") ))
Periksa apakah string yang diberikan adalah versi semantik dan mengembalikan bentuk yang dinormalisasi (tanpa memimpin v
dan bagian rilis lengkap dengan nomor versi jurusan, minor dan dan patch).
misalnya:
normalized : (( semver("v1.2-beta.1") ))
menyelesaikan untuk
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
Kembalikan bagian rilis dari versi semantik menghilangkan informasi metadata dan prerelease.
misalnya:
release : (( semverrelease("v1.2.3-beta.1") ))
menyelesaikan untuk
release : v1.2.3
Jika argumen string tambahan diberikan fungsi ini menggantikan rilis dengan rilis versi semantik yang diberikan melestarikan informasi metadata dan prerelease.
misalnya:
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
menyelesaikan untuk
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
Tentukan nomor utama dari versi semantik yang diberikan. Hasilnya adalah bilangan bulat.
misalnya:
major : (( semvermajor("1.2.3-beta.1") ))
menyelesaikan untuk
major : 1
Fungsi semverincmajor
dapat digunakan untuk menambah nomor versi utama dan mengatur ulang versi minor, versi patch dan melepaskan sufiks.
misalnya:
new : (( semverincmajor("1.2.3-beta.1") ))
menyelesaikan untuk
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
Tentukan nomor versi minor dari versi semantik yang diberikan. Hasilnya adalah bilangan bulat.
misalnya:
minor : (( semverminor("1.2.3-beta.1") ))
menyelesaikan untuk
minor : 2
Fungsi semverincminor
dapat digunakan untuk menambah nomor versi minor dan mengatur ulang versi patch dan melepaskan sufiks.
misalnya:
new : (( semverincmajor("v1.2.3-beta.1") ))
menyelesaikan untuk
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
Tentukan nomor versi patch dari versi semantik yang diberikan. Hasilnya adalah bilangan bulat.
misalnya:
patch : (( semverpatch("1.2.3-beta.1") ))
menyelesaikan untuk
patch : 3
Fungsi semverincpatch
dapat digunakan untuk menambah nomor versi tambalan atau mengatur ulang sufiks rilis. Jika ada sufiks rlease, mereka dihapus dan info rilis tetap tidak berubah, jika tidak, nomor versi tambalan meningkat.
misalnya:
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
menyelesaikan untuk
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
Tentukan prerlease dari versi semantik yang diberikan. Hasilnya adalah string.
misalnya:
prerelease : (( semverprerelease("1.2.3-beta.1") ))
menyelesaikan untuk
prerelease : beta.1
Jika argumen string tambahan diberikan set fungsi ini, mengganti atau menghapus (jika diatur ke string kosong) prerelease
misalnya:
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
menyelesaikan untuk
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
Tentukan metadata versi semantik yang diberikan. Hasilnya adalah string.
misalnya:
metadata : (( semvermetadata("1.2.3+demo") ))
menyelesaikan untuk
metadata : demo
Jika argumen string tambahan diberikan fungsi ini, mengganti atau menghapus (jika diatur ke string kosong) metadata.
misalnya:
new : (( semvermetadata("1.2.3-test", "demo) ))
menyelesaikan untuk
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
Bandingkan dua versi semantik. Prerlease selalu lebih kecil dari rilis terakhir. Hasilnya adalah bilangan bulat dengan nilai -nilai berikut:
hasil | arti |
---|---|
-1 | Versi pertama adalah sebelum versi kedua |
0 | Kedua versi itu sama |
1 | Versuon pertama adalah setelah yang kedua |
misalnya:
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
menyelesaikan untuk
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
Cocokkan versi semantik yang diberikan dengan daftar kontraint. Hasilnya adalah boolean. Dimungkinkan untuk menentukan sejumlah kendala versi. Jika tidak ada kendala yang diberikan, fungsi hanya memeriksa apakah string yang diberikan adalah versi semantik.
misalnya:
match : (( semvermatch("1.2.3", "~1.2") ))
menyelesaikan untuk
match : true
Daftar lengkap spesifikasi kendala yang mungkin dapat ditemukan di sini.
(( semversort("1.2.3", "1.2.1") ))
Urutkan daftar versi dalam urutan naik. v
terkemuka dilestarikan.
misalnya:
sorted : (( semversort("1.2.3", "1.2.1") ))
menyelesaikan untuk
sorted :
- 1.2.1
- 1.2.3
Daftar versi yang akan diurutkan juga dapat ditentukan dengan argumen daftar tunggal.
Spiff mendukung beberapa fungsi yang berguna untuk bekerja dengan sertifikat dan kunci X509 . Silakan merujuk juga ke bagian yang bermanfaat untuk mengetahui untuk menemukan beberapa tips untuk menyediakan negara.
(( x509genkey(spec) ))
Fungsi ini dapat digunakan menghasilkan kunci RSA atau ECDSA pribadi. Hasilnya akan menjadi kunci yang dikodekan PEM sebagai nilai string multi -line. Jika ukuran kunci (integer atau string) diberikan sebagai argumen, kunci RSA akan dihasilkan dengan ukuran kunci yang diberikan (misalnya 2048). Diberi salah satu nilai string
Fungsi ini akan menghasilkan kunci ECDSA yang sesuai.
misalnya:
keys :
key : (( x509genkey(2048) ))
menyelesaikan sesuatu seperti
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
Untuk kunci atau sertifikat yang diberikan dalam format PEM (misalnya dihasilkan dengan fungsi X509GenKey) fungsi ini mengekstraksi kunci publik dan mengembalikannya lagi dalam format PEM sebagai string multi-line.
misalnya:
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
menyelesaikan sesuatu seperti
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-----
Untuk menghasilkan kunci publik SSH, argumen format tambahan opsional dapat diatur ke ssh
. Hasilnya kemudian akan menjadi format kunci publik reguler yang dapat digunakan untuk SSH. Format default adalah pem
yang menyediakan format output PEM yang ditunjukkan di atas.
Kunci RSA secara default marshalled dalam format PKCS#1 ( RSA PUBLIC KEY
) di PEM. Jika format PKIX generik ( PUBLIC KEY
) diperlukan, argumen format, pkix
harus diberikan.
Menggunakan Format ssh
Fungsi ini juga dapat digunakan untuk mengubah kunci publik yang diformat PEM menjadi kunci SSH,
(( x509cert(spec) ))
Fungsi x509cert
membuat sertifikat yang ditandatangani secara lokal, baik yang ditandatangani sendiri atau sertifikat yang ditandatangani oleh ca yang diberikan. Ini mengembalikan sertifikat yang dikodekan PEM sebagai nilai string multi-line.
Parameter SPEC tunggal mengambil peta dengan beberapa bidang opsional dan non opsional yang digunakan untuk menentukan informasi sertifikat. Ini bisa berupa ekspresi peta inline atau referensi peta apa pun ke dalam dokumen YAML lainnya.
Bidang peta berikut diamati:
Nama Lapangan | Jenis | Diperlukan | Arti |
---|---|---|---|
commonName | rangkaian | opsional | Bidang Nama Umum Subjek |
organization | Daftar String atau String | opsional | Bidang organisasi subjek |
country | Daftar String atau String | opsional | Bidang negara subjek |
isCA | bodoh | opsional | Opsi CA Sertifikat |
usage | Daftar String atau String | diperlukan | Kunci Penggunaan untuk Sertifikat (lihat di bawah) |
validity | bilangan bulat | opsional | interval validitas dalam jam |
validFrom | rangkaian | opsional | Waktu mulai dalam format "1 Jan 01:22:31 2019" |
hosts | Daftar String atau String | opsional | Daftar Nama DNS atau Alamat IP |
privateKey | rangkaian | diperlukan atau publickey | kunci pribadi untuk menghasilkan sertifikat |
publicKey | rangkaian | diperlukan atau privateKey | kunci publik untuk menghasilkan sertifikat |
caCert | rangkaian | opsional | Sertifikat untuk menandatangani |
caPrivateKey | rangkaian | opsional | kunci priavte untuk caCert |
Untuk sertifikat yang ditandatangani sendiri, bidang privateKey
harus ditetapkan. publicKey
dan bidang ca
harus dihilangkan. Jika bidang caCert
diberikan, bidang caKey
juga diperlukan. Jika bidang privateKey
diberikan bersama dengan caCert
, kunci publik untuk sertifikat diekstraksi dari kunci pribadi.
Ladang tambahan diabaikan diam -diam.
Kunci penggunaan berikut didukung (kasus diabaikan):
Kunci | Arti |
---|---|
Signature | x509.KeyusagedIgitalSignature |
Commitment | x509.KeyUsagecontentCommitment |
KeyEncipherment | X509.KeyUsageKeyenciphime |
DataEncipherment | x509.keyusagedataencipherment |
KeyAgreement | x509.KeyUsageKeyAGREENTY |
CertSign | x509.KeyUsageCertSign |
CRLSign | x509.keyusagecrlsign |
EncipherOnly | x509.keyusageencipherely |
DecipherOnly | x509.keyusagedecipheronly |
Any | x509.ExtKeyUsageAny |
ServerAuth | x509.ExtKeyUsageServerauth |
ClientAuth | x509.ExtKeyUsageclientAuth |
codesigning | x509.extKeyUsagecodesigning |
EmailProtection | x509.extKeyUsageMailProtection |
IPSecEndSystem | x509.ExtKeyUsageIpsecendsystem |
IPSecTunnel | x509.ExtKeyUsageipsectunnel |
IPSecUser | x509.ExtKeyUsageIpsecuser |
TimeStamping | x509.extKeyUsagetimestamping |
OCSPSigning | x509.ExtKeyUsageOcspsigning |
MicrosoftServerGatedCrypto | x509.extKeyUsagemicrosoftServergatedCrypto |
NetscapeServerGatedCrypto | x509.extKeyUsagenetScapeservergatedCrypto |
MicrosoftCommercialCodeSigning | x509.extKeyUsagemicrosoftCommercialCodesigning |
MicrosoftKernelCodeSigning | x509.extKeyUsagemicrosoftKernelCodesigning |
misalnya:
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
menghasilkan sertifikat root yang ditandatangani sendiri dan menyelesaikan sesuatu seperti
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) ))
Fungsi ini mem -parsing sertifikat yang diberikan dalam format PEM dan mengembalikan peta bidang:
Nama Lapangan | Jenis | Diperlukan | Arti |
---|---|---|---|
commonName | rangkaian | opsional | Bidang Nama Umum Subjek |
organization | Daftar String | opsional | Bidang organisasi subjek |
country | Daftar String | opsional | Bidang negara subjek |
isCA | bodoh | selalu | Opsi CA Sertifikat |
usage | Daftar String | selalu | Kunci Penggunaan untuk Sertifikat (lihat di bawah) |
validity | bilangan bulat | selalu | interval validitas dalam jam |
validFrom | rangkaian | selalu | Waktu mulai dalam format "1 Jan 01:22:31 2019" |
validUntil | rangkaian | selalu | Waktu mulai dalam format "1 Jan 01:22:31 2019" |
hosts | Daftar String | opsional | Daftar Nama DNS atau Alamat IP |
dnsNames | Daftar String | opsional | Daftar Nama DNS |
ipAddresses | Daftar String | opsional | Daftar Alamat IP |
publicKey | rangkaian | selalu | kunci publik untuk menghasilkan sertifikat |
misalnya:
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) ))
menyelesaikan untuk
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.
misalnya:
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-
misalnya:
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. Ketika
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) ))
hasil
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.
misalnya:
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.
misalnya:
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.
misalnya:
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. Karena itu
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)
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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
.
misalnya:
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.
misalnya:
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
Perhatian :
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.
misalnya:
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.
misalnya:
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.
misalnya:
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. Atauselect
. 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.
misalnya
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
hasil
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.
misalnya
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
hasil
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.
misalnya
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
hasil
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
Komentar
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[]
misalnya
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
Komentar
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 .
Komentar
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.
misalnya
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
hasil
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.
misalnya
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
hasil
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.
misalnya
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
hasil
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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
misalnya:
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
.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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).)
misalnya:
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.
misalnya:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
misalnya:
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.
Misalnya:
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.
misalnya:
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
misalnya:
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:
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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:
Field Name | Jenis | Arti |
---|---|---|
VERSION | rangkaian | current version of spiff |
FILE | rangkaian | name of actually processed template file |
DIR | rangkaian | name of directory of actually processed template file |
RESOLVED_FILE | rangkaian | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | rangkaian | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | rangkaian | 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
.
misalnya:
template.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
misalnya:
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.
Misalnya
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.
misalnya:
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.
misalnya:
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.
.misalnya:
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 (( ~~ ))
.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
atau
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.
misalnya:
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:
misalnya:
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
kembali
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
dengan
stub.yml
foo :
alice : 24
bob : 26
hasil
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"] ))
dengan
stub.yml
foo :
- peter
- paul
hasil
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 ))
dengan
stub.yml
people :
- alice : 13
hasil
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. In the following example
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
hasil
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
misalnya:
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.
misalnya:
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.
misalnya:
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.
misalnya:
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
dengan
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
hasil
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
dengan
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
Dan
config.yml
config :
alice : 4711
peter : 0815
hasil
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.
misalnya:
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.
misalnya:
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 ))
Dan
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.
misalnya:
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):
Menandai | Arti |
---|---|
* | 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 ))
}
It supports