_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
는 spiff에서 아직 사용할 수 없는 풍부한 새로운 기능 세트를 제공하는 최신 버전을 기반으로 spiff에 호환 가능한 확장을 제공하는 spiff의 포크입니다. 원래 spiff 프로젝트에서 제공한 모든 수정 사항은 spiff++에도 통합됩니다. spiff 소스 기반으로 돌아갈 수 있는 방법이 없기 때문에 spiff++ 개발을 계속하기 위해 새로운 독립 spiff++ 저장소가 생성되었습니다.spiff 는 명령줄 도구이자 선언적 도메인 내 하이브리드 YAML 템플릿 시스템입니다. 일반 템플릿 시스템은 외부 데이터 소스에서 가져온 값으로 템플릿 표현식을 대체하여 템플릿 파일을 처리하는 반면, 도메인 내에서는 템플릿 엔진이 처리된 템플릿의 구문과 구조를 알고 있음을 의미합니다. 따라서 템플릿 표현식 자체로 표시된 부분을 포함하여 처리된 문서에서 직접 템플릿 표현식의 값을 가져올 수 있습니다.
예를 들어:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
외부 값 소스만 사용하는 대신 spiff는 템플릿을 병합 스텁과 병합하여 최종 문서를 생성하는 병합 메커니즘을 제공합니다.
이는 배포 매니페스트(예: BOSH, Kubernetes 또는 Landscaper 매니페스트) 생성을 위해 특별히 설계된 명령줄 도구이자 선언적 YAML 템플릿 시스템입니다.
CLI 외에도 모든 GO 프로그램(예: Landscaper)에서 spiff 템플릿 처리를 사용할 수 있는 golang 라이브러리가 있습니다.
템플릿 엔진은 구성 가능한 가상 파일 시스템 또는 프로세스 시스템을 기반으로 하는 파일 시스템에 대한 액세스를 제공하여 명령을 실행하고 출력을 템플릿 처리에 통합합니다.
내용물:
<<if:
<<switch:
<<type:
<<for:
<<merge:
공식 릴리스 실행 바이너리는 Github 릴리스를 통해 Darwin, Linux 및 PowerPC 머신(및 가상 머신)용으로 다운로드할 수 있습니다.
마지막 공식 릴리스 이후 일부 spiff의 종속성이 변경되었으며 spiff는 이러한 종속성을 따라잡기 위해 업데이트되지 않습니다. 이러한 종속성은 고정되거나 로컬 코드 베이스에 복사됩니다.
spiff merge template.yml [template2.yml ...]
여러 템플릿 파일을 하나의 매니페스트로 병합하여 인쇄합니다.
템플릿 파일에 대한 자세한 내용은 '동적 템플릿 언어'를 참조하고, 더 복잡한 예는 example/subdir을 참조하세요.
예:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
파일 이름 -
을 사용하여 표준 입력에서 하나의 파일을 읽을 수 있습니다. 한 번만 사용할 수 있습니다. 이를 통해 단일 스트림을 처리하거나 여러 템플릿/스텁을 기반으로 스트림을 처리하기 위해 파이프라인의 일부로 spiff를 사용할 수 있습니다.
템플릿 파일(첫 번째 인수)은 ---
만 포함하는 줄로 구분된 여러 YAML 문서를 포함하는 여러 문서 스트림일 수 있습니다. 각 YAML 문서는 지정된 스텁 파일과 독립적으로 처리됩니다. 결과는 동일한 순서로 처리된 문서의 스트림입니다. 문서의 루트 노드가 임시로 표시되면 해당 문서는 출력 스트림에서 생략됩니다. 예를 들어 kubectl
에서 사용할 kubernetes 매니페스트를 생성하는 데 사용할 수 있습니다.
merge
명령은 다음과 같은 몇 가지 옵션을 제공합니다.
--partial
옵션. 이 옵션이 주어지면 spiff는 불완전한 표현식 평가를 처리합니다. 모든 오류는 무시되고 yaml 문서의 확인할 수 없는 부분은 문자열로 반환됩니다.
--json
옵션을 사용하면 출력이 YAML 대신 JSON 형식이 됩니다.
--path <path>
옵션을 사용하면 처리된 전체 문서 대신 중첩된 경로를 출력할 수 있습니다.
출력이 목록인 경우 --split
옵션은 모든 목록 요소를 별도의 문서로 출력합니다. yaml 형식은 평소와 같이 ---
구분선으로 사용합니다. json 형식은 한 줄에 하나씩 일련의 json 문서를 출력합니다.
--select <field path>
를 사용하면 출력을 위해 처리된 문서의 전용 필드를 선택할 수 있습니다.
--evaluate <dynaml expression>
사용하면 출력을 위해 처리된 문서에서 주어진 동적 표현식을 평가할 수 있습니다. 선택 경로가 적용되기 전에 표현식이 평가되어 평가 결과에 적용됩니다.
--state <path>
옵션은 spiff 의 상태 지원을 활성화합니다. 지정된 파일이 존재하는 경우 지정된 파일에 대해 구성된 스텁 목록 맨 위에 배치됩니다. 병합 처리를 위해 구성된 스텁 목록 맨 위에 배치됩니다. 추가적으로 처리된 문서의 출력에 &state
마커가 표시된 노드에 대해 필터링됩니다. 이 필터링된 문서는 지정된 파일 아래에 저장되며 .bak
접미사가 있는 이전 상태 파일이 저장됩니다. 이는 상태 유틸리티 라이브러리에서 제공하는 수동 병합과 함께 사용할 수 있습니다.
--bindings <path>
옵션을 사용하면 처리를 위한 추가 바인딩을 구축하는 데 해당 콘텐츠가 사용되는 yaml 파일을 지정할 수 있습니다. yaml 문서는 맵으로 구성되어야 합니다. 각 키는 추가 바인딩으로 사용됩니다. 바인딩 문서는 처리되지 않으며 값은 정의된 대로 사용됩니다.
--tag <tag>:<path>
옵션을 사용하면 yaml 파일을 지정할 수 있으며, 그 내용은 사전 정의된 전역 태그의 값으로 사용됩니다(태그 참조). 태그는 <tag>::<ref>
형식의 참조 표현식으로 액세스할 수 있습니다. 바인딩과 달리 태그가 지정된 콘텐츠는 문서의 노드와 경쟁하지 않으며 다른 참조 네임스페이스를 사용합니다.
옵션 --define <key>=<value>
(약어 -D
)를 사용하면 바인딩 파일의 바인딩 값을 재정의하여 명령줄에 추가 바인딩 값을 지정할 수 있습니다. 이 옵션은 여러 번 발생할 수 있습니다.
키 에 점( .
)이 포함되어 있으면 딥맵 값의 필드를 설명하는 경로 표현으로 해석됩니다. 점(및 점 앞의 )은
로 이스케이프 처리하여 필드 이름에 유지할 수 있습니다.
--preserve-escapes
옵션은 동적 표현식 및 목록/맵 병합 지시문에 대한 이스케이프를 유지합니다. spiff 를 사용한 처리 결과의 추가 처리 단계가 의도된 경우 이 옵션을 사용할 수 있습니다.
--preserve-temporary
옵션은 최종 문서에서 임시로 표시된 필드를 보존합니다.
--features=<featurelist>
옵션은 이 특정 기능을 활성화합니다. 이전 동작과 호환되지 않는 새로운 기능은 명시적으로 활성화되어야 합니다. 일반적으로 이러한 기능은 일반적인 동작을 중단하지 않지만 이전에 일반 값으로 사용되었던 yaml 값에 대한 전용 해석을 도입합니다.
폴더 라이브러리는 몇 가지 유용한 유틸리티 라이브러리를 제공합니다. 또한 이 템플릿 엔진의 성능에 대한 예로 사용할 수도 있습니다.
spiff diff manifest.yml other-manifest.yml
두 배포 매니페스트 간의 구조적 차이점을 보여줍니다. 여기에서는 여러 문서가 포함된 스트림도 지원됩니다. 차이가 없음을 나타내려면 두 스트림의 문서 수가 동일해야 하며 첫 번째 스트림의 각 문서는 두 번째 스트림의 동일한 인덱스를 가진 문서와 비교할 때 차이가 없어야 합니다. 발견된 차이점은 각 문서마다 별도로 표시됩니다.
기본 비교 도구 및 bosh diff
와 달리 이 명령은 배포 매니페스트에 대한 의미론적 지식을 갖고 있으며 단순한 텍스트 기반이 아닙니다. 예를 들어, 두 매니페스트가 동일한 경우 일부 작업이 서로 다른 순서로 나열되어 있다는 점을 제외하면 매니페스트에서 작업 순서가 중요하므로 spiff diff
이를 감지합니다. 반면, 예를 들어 두 매니페스트가 리소스 풀 순서만 다른 경우 리소스 풀 순서는 실제로 배포에 중요하지 않기 때문에 차이가 발생하고 비어 있게 됩니다.
또한 bosh diff
와 달리 이 명령은 두 파일을 모두 수정하지 않습니다.
한 배포와 다음 배포 간의 차이점을 확인하기 위한 것입니다.
일반적인 흐름:
$ spiff merge template.yml [templates...] > deployment.yml
$ bosh download manifest [deployment] current.yml
$ spiff diff deployment.yml current.yml
$ bosh deployment deployment.yml
$ bosh deploy
spiff convert --json manifest.yml
convert
하위 명령을 사용하여 입력 파일을 json으로 변환하거나 필드 순서를 정규화할 수 있습니다. 사용 가능한 옵션은 merge
하위 명령의 의미에 따라 --json
, --path
, --split
또는 --select
입니다.
spiff encrypt secret.yaml
encrypt
하위 명령을 사용하면 encrypt
동적 기능에 따라 데이터를 암호화하거나 해독할 수 있습니다. 비밀번호는 두 번째 인수로 제공되거나 환경 변수 SPIFF_ENCRYPTION_KEY
에서 가져올 수 있습니다. 마지막 인수는 암호화 방법을 전달하는 데 사용될 수 있습니다( encrypt
기능 참조).
데이터는 지정된 파일에서 가져옵니다. -
주어지면 stdin에서 읽습니다.
-d
옵션이 주어지면 데이터가 복호화되고, 그렇지 않으면 데이터를 yaml 문서로 읽어 암호화된 결과가 인쇄됩니다.
이전 동작과 호환되지 않는 새로운 기능은 명시적으로 활성화되어야 합니다. 일반적으로 이러한 기능은 일반적인 동작을 손상시키지 않지만 이전에 일반 값으로 사용되었던 yaml 값에 대한 전용 해석을 도입하므로 기존 사용 사례를 손상시킬 수 있습니다.
현재 지원되는 기능 플래그는 다음과 같습니다.
특징 | 부터 | 상태 | 의미 |
---|---|---|---|
interpolation | 1.7.0-베타-1 | 알파 | yaml 문자열의 일부인 dynaml |
control | 1.7.0-베타-4 | 알파 | yaml 기반 제어 구조 |
활성 기능 플래그는 dynaml 함수 features()
문자열 목록으로 사용하여 쿼리할 수 있습니다. 이 함수가 문자열 인수와 함께 호출되면 해당 기능이 현재 활성화되어 있는지 여부를 반환합니다.
--features
옵션을 사용하는 명령줄, WithFeatures
기능을 사용하는 go 라이브러리 또는 일반적으로 환경 변수 SPIFF_FEATURES
기능 목록으로 설정하여 기능을 활성화할 수 있습니다. 이 설정은 항상 기본값으로 사용되었습니다. go 라이브러리의 Plain()
spiff 설정을 사용하면 모든 환경 변수가 무시됩니다.
기능은 이름으로 지정하거나 이름 앞에 접두사 no
붙여 지정하여 비활성화할 수 있습니다.
라이브러리 폴더에는 몇 가지 유용한 spiff 템플릿 라이브러리가 포함되어 있습니다. 이는 기본적으로 병합 처리를 위한 유틸리티 기능을 제공하기 위해 병합 파일 목록에 추가되는 스텁입니다.
Spiff는 'dynaml'(동적 yaml)이라는 선언적이고 논리가 없는 템플릿 언어를 사용합니다.
모든 dynaml 노드는 YAML 노드로 확인되도록 보장됩니다. 문자열 보간이 아닙니다 . 이렇게 하면 개발자가 결과 템플릿에서 값이 어떻게 렌더링되는지 생각할 필요가 없습니다.
dynaml 노드는 두 개의 괄호 (( <dynaml> ))
로 둘러싸인 표현식을 나타내는 문자열로 .yml 파일에 나타납니다. 이는 맵의 값이나 목록의 항목으로 사용될 수 있습니다. 표현식은 여러 줄에 걸쳐 있을 수 있습니다. 어떤 경우에도 yaml 문자열 값은 줄바꿈으로 끝나서 는 안 됩니다 (예: |-
사용).
괄호로 묶인 값을 동적 표현식으로 해석하지 않고 출력에 있는 그대로 유지해야 하는 경우 여는 괄호 바로 뒤에 느낌표를 사용하여 이스케이프할 수 있습니다.
예를 들어 ((! .field ))
문자열 값 (( .field ))
에 매핑되고 ((!! .field ))
문자열 값 ((! .field ))
에 매핑됩니다.
다음은 동적 표현식의 전체 목록입니다.
(( foo ))
현재 템플릿에서 가장 가까운 'foo' 키(예: 어휘 범위 지정)를 찾아 가져옵니다.
예:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
이 예에서는 다음을 해결합니다.
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
키 이름이 병합할 값과 동일하기 때문에 다음은 해결되지 않습니다.
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
가장 가까운 'foo' 키를 찾아 거기서부터 .bar.[1].baz
까지 따라갑니다.
경로는 점으로 구분된 일련의 단계입니다. 단계는 맵의 경우 단어이거나 목록 인덱싱의 경우 괄호로 묶인 숫자입니다. 색인은 음수일 수 있습니다(음수 다음에 숫자가 옴). 음수 인덱스는 목록의 끝에서 가져옵니다(유효 인덱스 = 인덱스 + 길이(목록)).
확인할 수 없는 경로는 평가 오류로 이어집니다. 가끔 참조가 제공되지 않을 것으로 예상되는 경우 '||'와 함께 사용해야 합니다. (아래 참조) 해상도를 보장합니다.
참고 : 이제 일반적인 인덱스 구문을 사용할 수 있도록 dynaml 문법이 재작업되었습니다. foo.bar.[1]
대신 이제 foo.bar[1]
사용할 수 있습니다.
참고 : 참조는 항상 템플릿이나 스텁 내에 있으며 순서는 중요하지 않습니다. 다른 동적 노드를 참조하여 해결되었다고 가정할 수 있으며 참조 노드는 종속 노드가 해결되면 결국 해결됩니다.
예:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
'무언가'가 해결 가능하고 다음과 같은 결과가 발생하는 한 해결됩니다.
from :
the :
stub : foo
경로가 점( .
)으로 시작하는 경우 경로는 항상 문서의 루트에서 평가됩니다. 문서 루트가 목록인 경우 첫 번째 맵 수준은 .__map
으로 시작하는 경우 경로 표현식을 확인하는 데 사용됩니다. 이는 목록 항목이 추가되면 변경될 수 있는 자체 목록 인덱스(예 .[1].path
)를 사용할 필요를 피하기 위해 사용할 수 있습니다.
name
필드가 있는 맵으로 구성된 목록 항목은 경로 구성 요소인 이름 값으로 직접 주소를 지정할 수 있습니다.
참고 : 이는 목록 문서의 절대 경로에도 적용됩니다.
예:
앨리스의 나이
list :
- name : alice
age : 25
list[0].age
대신 list.alice.age
경로를 사용하여 참조할 수 있습니다.
기본적으로 name
인 필드가 키 필드로 사용됩니다. 다른 필드를 키 필드로 사용해야 하는 경우 필드 이름 앞에 키워드 key:
추가하여 하나의 목록 항목에서 키로 표시할 수 있습니다. 이 키워드는 처리 과정에서 제거되며 최종 처리 결과에 포함되지 않습니다.
예:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
로 해결될 것이다
list :
- person : alice
age : 25
alice :
person : alice
age : 25
이 새로운 키 필드는 목록을 병합하는 동안에도 관찰됩니다.
선택한 키 필드가 !
로 시작하는 경우 , 주요 기능이 비활성화됩니다. 유효 필드 이름에서도 느낌표가 제거됩니다.
키 필드의 값이 고유하지 않으면 비활성화됩니다.
(( foo.[bar].baz ))
가장 가까운 'foo' 키를 찾고 거기서부터 표현식 bar
에 설명된 필드를 따라가다가 .baz로 이동합니다.
인덱스는 마지막 섹션에 설명된 대로 정수 상수(공백 없음)일 수 있습니다. 그러나 임의의 동적 표현식일 수도 있습니다(정수이지만 공백이 있음). 표현식이 문자열로 평가되면 전용 필드를 조회합니다. 표현식이 정수로 평가되면 이 인덱스가 있는 배열 요소의 주소가 지정됩니다. 인덱스 연산자 앞의 점( .
)은 선택 사항입니다.
예:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
그러면 foo
값이 42
로 확인됩니다. 동적 인덱스는 표현식 끝에 있을 수도 있습니다( .bar
제외).
기본적으로 이는 eval("values." name ".bar") 와 같은 것을 표현하는 더 간단한 방법입니다.
표현식이 목록으로 평가되는 경우 목록 요소(문자열 또는 정수)는 더 깊은 필드에 액세스하기 위한 경로 요소로 사용됩니다.
예:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
foo
값 42
로 다시 확인합니다.
참고 : 인덱스 연산자는 루트 요소( .[index]
)에서도 사용할 수 있습니다.
연속적인 목록에 여러 개의 쉼표로 구분된 표시를 지정하는 것이 가능합니다( foo[0][1]
은 `foo[0,1]과 동일합니다). 그러한 경우 색인은 다시 목록이 될 수 없습니다.
(( list.[1..3] ))
슬라이스 표현식은 목록 표현식에서 전용 하위 목록을 추출하는 데 사용할 수 있습니다. start ..
end 범위는 start 에서 end 인덱스까지의 요소가 포함된 end-start+1 길이의 목록을 추출합니다. 시작 인덱스가 음수이면 목록의 끝에서 length+start 에서 length+end 까지 슬라이스를 가져옵니다. 끝 인덱스가 시작 인덱스보다 낮으면 결과는 빈 배열입니다.
예:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
시작 또는 끝 인덱스는 생략될 수 있습니다. 그런 다음 목록의 실제 크기에 따라 선택됩니다. 따라서 list.[1..length(list)]
list.[1..]
과 동일합니다.
foo
목록 [b,c]
로 평가합니다.
(( 1.2e4 ))
숫자 리터럴은 정수 및 부동 소수점 값에 지원됩니다.
(( "foo" ))
문자열 리터럴. 모든 json 문자열 인코딩이 지원됩니다(예: n
, "
또는 uxxxx
).
(( [ 1, 2, 3 ] ))
리터럴을 나열합니다. 목록 요소는 다시 표현식일 수 있습니다. 증가 또는 감소하는 숫자 범위를 목록으로 해결하는 데 사용할 수 있는 특수 목록 리터럴 [1 .. -1]
이 있습니다.
예:
list : (( [ 1 .. -1 ] ))
수확량
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
맵 리터럴은 동적 표현식의 일부로 맵을 설명하는 데 사용할 수 있습니다. 키와 값 모두 표현식일 수 있으므로 키 표현식은 문자열로 평가되어야 합니다. 이렇게 하면 비정적 키를 사용하여 맵을 생성할 수 있습니다. yaml에 사용되는 일반 콜론 :
문자 대신 할당 연산자 =
선택되었습니다. 이는 yaml 구문과 충돌이 발생하기 때문입니다.
맵 리터럴은 쉼표( )로 구분된 필드 할당으로 구성될 수 있습니다 ,
예:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
수확량
name : peter
age : 23
map :
alice : {}
peter : 23
표현식을 기반으로 목록을 작성하는 또 다른 방법은 makemap
및 list_to_map
함수입니다.
(( ( "alice" = 25 ) alice ))
모든 표현식에는 명시 적 범위 리터럴이 얼마든지 포함될 수 있습니다. 범위 리터럴은 표현식(정적 범위)의 상대 참조 확인에 사용할 수 있는 값이 있는 맵을 설명합니다. 주어진 이름에 대한 추가 로컬 바인딩을 생성합니다.
범위 리터럴은 쉼표( )로 구분된 필드 할당 수로 구성될 수 있습니다 ,
키와 값은 표현식으로 제공되는 반면, 키 표현식은 문자열로 평가되어야 합니다. 모든 표현식은 다음 외부 범위에서 평가됩니다. 이는 범위의 이후 설정이 동일한 범위 리터럴의 이전 설정을 사용할 수 없음 을 의미합니다.
예:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
수확량
scoped : 51
필드 이름은 기호( $
name )로 표시될 수도 있습니다.
(( foo bar ))
일련의 동적 표현식을 연결하는 데 사용되는 연결 표현식입니다.
(( "foo" bar ))
연결(bar는 또 다른 dynaml expr임) 간단한 값(문자열, 정수 및 부울)의 시퀀스는 임의의 동적 표현식으로 제공되어 연결될 수 있습니다.
예:
domain : example.com
uri : (( "https://" domain ))
이 예에서 uri
"https://example.com"
값으로 확인됩니다.
(( [1,2] bar ))
목록을 표현식으로 연결합니다(여기서 bar는 또 다른 동적 표현식입니다). dynaml 표현식을 사용하여 목록의 모든 시퀀스를 연결할 수 있습니다.
예:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
이 예에서 static_ips
[ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
값으로 확인됩니다.
두 번째 표현식이 목록(정수, 부울, 문자열 또는 맵) 이외의 값으로 평가되면 해당 값이 첫 번째 목록에 추가됩니다.
예:
foo : 3
bar : (( [1] 2 foo "alice" ))
bar
에 대한 목록 [ 1, 2, 3, "alice" ]
를 생성합니다.
(( map1 map2 ))
표현식으로 맵을 연결합니다. 임의의 dynaml 표현식을 통해 임의의 맵 시퀀스를 연결할 수 있습니다. 그러면 항목이 병합됩니다. 동일한 키를 가진 항목은 왼쪽에서 오른쪽으로 덮어쓰여집니다.
예:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
수확량
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
상황에 맞는 자동 값 계산.
리소스 풀의 '크기' 속성에서 이는 현재 리소스 풀에 있다고 선언된 모든 작업의 총 인스턴스를 기반으로 계산한다는 의미입니다.
예:
resource_pools :
- name : mypool
size : (( auto ))
jobs :
- name : myjob
resource_pool : mypool
instances : 2
- name : myotherjob
resource_pool : mypool
instances : 3
- name : yetanotherjob
resource_pool : otherpool
instances : 3
이 경우 리소스 풀 크기는 '5'로 확인됩니다.
(( merge ))
병합되는 스텁 파일에서 현재 경로를 가져옵니다.
예:
foo :
bar :
baz : (( merge ))
첫 번째 스텁이나 두 번째 등에서 foo.bar.baz
가져오려고 시도하고 이를 제공하는 마지막 스텁에서 값을 반환합니다.
해당 값이 정의되지 않은 경우 nil을 반환합니다. 이는 참조 표현식과 동일한 의미를 갖습니다. nil 병합은 해결되지 않은 템플릿입니다. 참조 ||
.
<<: (( merge ))
일부 스텁에서 발견된 동일한 요소의 콘텐츠와 맵 또는 목록을 병합합니다.
** 주의 ** 이 형태의 merge
에는 호환성 문제가 있습니다. 1.0.8 이전 버전에서는 이 표현식이 구문 분석되지 않았으며 <<:
키의 존재만 관련이 있었습니다. 따라서 <<: (( merge ))
<<: (( merge || nil ))
의 사용법이 종종 있습니다. 첫 번째 변형은 적어도 하나의 스텁에 콘텐츠가 필요합니다(항상 병합 연산자의 경우). 이제 이 표현식은 올바르게 평가되지만 이로 인해 첫 번째 변형을 사용하지만 두 번째 변형을 의미하는 기존 매니페스트 템플릿 세트가 손상됩니다. 따라서 이 경우는 선택적 병합을 설명하기 위해 명시적으로 처리됩니다. 실제로 필요한 병합을 의미하는 경우 추가적인 명시적 한정자가 있어야 합니다.
참고 : <<:
삽입 필드를 사용하여 병합 표현식을 배치하는 대신 이제 <<<:
사용할 수도 있습니다. 이를 통해 spiff와 같은 yaml 문서에 일반 yaml 파서를 사용할 수 있습니다. <<:
이전 버전과의 호환성을 위해 유지됩니다. ( (( merge required ))
)를 사용합니다.
병합 키가 병합 지시문 대신 일반 키로 해석되어서는 안 되는 경우 느낌표( !
)로 이스케이프할 수 있습니다.
예를 들어 맵 키 <<<!
문자열 키 <<<
및 <<<!!
가 생성됩니다. 문자열 키 <<<!
값.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
다음을 생성합니다.
foo :
a : 1
b : 2
c : 4
값.yml
foo :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
다음을 생성합니다.
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
맵 목록을 키 필드와 병합할 수 있습니다. 이러한 목록은 키 필드의 값을 키로 사용하여 맵처럼 처리됩니다. 기본적으로 키 name
사용됩니다. 그러나 임의의 키 이름 on
선택기를 사용하면 목록 병합 표현식에 대해 지정할 수 있습니다.
예:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
와 합병
list :
- key : alice
age : 20
- key : peter
age : 13
수확량
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
(삽입 병합 표현식에서 요청한 대로) 새 항목을 삽입하지 않고 기존 항목만 재정의하는 경우 기존 키 필드 하나에 태그 key:
예를 들어 비표준 키 이름 - key:key: alice
나타냅니다. - key:key: alice
.
<<: (( merge replace ))
기존 콘텐츠에 대한 심층 병합을 수행하는 대신 요소의 전체 콘텐츠를 일부 스텁에서 찾은 콘텐츠로 바꿉니다.
값.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
다음을 생성합니다.
foo :
a : 1
b : 2
값.yml
foo :
- 1
- 2
template.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
다음을 생성합니다.
foo :
- 1
- 2
<<: (( foo ))
동일한 템플릿이나 스텁에 있는 맵과 목록을 병합합니다.
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
수익률:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
이 표현식은 실제 목록에 새 항목을 추가합니다. 기존 항목을 병합 표현식에 설명된 내용과 병합하지 않습니다.
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
수익률:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
이에 대한 일반적인 사용 사례는 고정 IP 또는 범위 목록을 IP 목록으로 병합하는 것입니다. 또 다른 가능성은 단일 연결 표현식을 사용하는 것입니다.
<<: (( merge foo ))
일부 스텁에서 발견된 임의 요소의 콘텐츠와 맵 또는 목록을 병합합니다(리디렉션 병합). 일부 스텁에서 발견된 동일한 이름의 요소와 더 이상 (깊은) 병합이 없습니다. (목록의 심층 병합에는 필드 name
이 있는 맵이 필요합니다.)
병합 리디렉션을 직접 필드 값으로 사용할 수도 있습니다. (( merge replace foo ))
와 같은 대체 병합과 결합될 수 있습니다.
값.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
다음을 생성합니다.
foo :
a : 1
b : 2
c : 4
일부 스텁의 다른 요소와 병합을 수행하는 또 다른 방법은 전통적인 방식으로 수행할 수도 있습니다.
값.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
그러나 이 시나리오에서는 병합이 원래 요소 이름과의 완전 병합을 계속 수행합니다. 따라서 spiff merge template.yml values.yml
다음을 생성합니다.
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
값.yml
foo :
- 10
- 20
bar :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
다음을 생성합니다.
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
리디렉션 병합의 참조가 상수 none
으로 설정되면 병합이 전혀 수행되지 않습니다. 이 표현식은 항상 nil 값을 생성합니다.
예: for
template.yml
map :
<< : (( merge none ))
value : notmerged
값.yml
map :
value : merged
spiff merge template.yml values.yml
다음을 생성합니다.
map :
value : notmerged
이는 업스트림 스텁의 전용 부분에 액세스하기 위해 stub
기능을 사용하여 명시적인 필드 병합에 사용할 수 있습니다.
예:
template.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
값.yml
map :
value : bob
spiff merge template.yml values.yml
다음을 생성합니다.
test :
value : alice+bob
이는 전용 필드에도 적용됩니다.
template.yml
map :
value : (( merge none // "alice" "+" stub() ))
값.yml
map :
value : bob
spiff merge template.yml values.yml
다음을 생성합니다.
test :
value : alice+bob
(( a || b ))
a를 확인할 수 없는 경우 a 또는 b를 사용합니다.
예:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
이는 mything.complicated_structure
에서 병합을 시도하거나, 병합할 수 없는 경우 foo.bar
에 지정된 기본값을 사용합니다.
//
연산자는 a
유효한 값( ~
와 같지 않음)으로 풀 수 있는지 여부를 추가로 확인합니다.
(( 1 + 2 * foo ))
Dynaml 표현식을 사용하여 산술 정수 및 부동 소수점 계산을 실행할 수 있습니다. 지원되는 작업은 +
, -
, *
및 /
입니다. 모듈로 연산자( %
)는 정수 피연산자만 지원합니다.
예:
값.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
bar
에 대해 7
생성합니다. 이는 연결과 결합될 수 있습니다(동적 표현식에서는 계산이 연결보다 우선순위가 높습니다).
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
결과는 문자열 3 times 2 yields 6
입니다.
(( "10.10.10.10" - 11 ))
정수 산술 외에도 IP 주소와 CIDRS에 대한 덧셈과 뺄셈을 사용할 수도 있습니다.
예:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
수확량
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
빼기는 두 IP 주소 또는 cidr에 대해서도 작동하여 두 IP 주소 사이의 IP 주소 수를 계산합니다.
예:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
값 256을 산출합니다. IP 주소 상수는 동적 표현식에서 직접 사용될 수 있습니다. 작업에 필요한 경우 암시적으로 문자열로 변환되고 다시 IP 주소로 변환됩니다.
곱셈과 나눗셈을 사용하여 CIDR의 IP 범위 이동을 처리할 수 있습니다. 분할을 통해 네트워크를 분할할 수 있습니다. 원래 CIDR 아래에 최소한 전용 서브넷 수를 허용하도록 네트워크 크기가 늘어납니다. 그런 다음 곱셈을 사용하여 동일한 크기의 n번째 다음 서브넷을 얻을 수 있습니다.
예:
subnet : (( "10.1.2.1/24" / 12 )) # first subnet CIDR for 16 subnets
next : (( "10.1.2.1/24" / 12 * 2)) # 2nd next (3rd) subnet CIDRS
수확량
subnet : 10.1.2.0/28
next : 10.1.2.32/28
또한 IPv4 CIDR에서 작동하는 기능이 있습니다.
cidr : 192.168.0.1/24
range : (( min_ip(cidr) "-" max_ip(cidr) ))
next : (( max_ip(cidr) + 1 ))
num : (( min_ip(cidr) "+" num_ip(cidr) "=" min_ip(cidr) + num_ip(cidr) ))
contains : (( contains_ip(cidr, "192.168.0.2") ))
수확량
cidr : 192.168.0.1/24
range : 192.168.0.0-192.168.0.255
next : 192.168.1.0
num : 192.168.0.0+256=192.168.1.0
contains : true
(( a > 1 ? foo :bar ))
Dynaml은 비교 연산자 <
, <=
, ==
, !=
, >=
및 >
를 지원합니다. 비교 연산자는 정수 값에 대해 작동합니다. 동등성 검사는 목록과 지도에서도 작동합니다. 결과는 항상 부울 값입니다. 조건을 부정하려면 단항 연산자( !
)를 사용할 수 있습니다.
또한 조건에 따라 표현식을 평가하는 데 사용할 수 있는 삼항 조건 연산자 ?:
가 있습니다. 첫 번째 피연산자가 조건으로 사용됩니다. 조건이 true이면 표현식은 두 번째 피연산자로 평가되고, 그렇지 않으면 세 번째 피연산자로 평가됩니다.
예:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
name
속성에 대해 bob
값을 생성합니다.
표현식이 다음과 같이 평가되면 false
으로 간주됩니다.
false
그렇지 않으면 그것은 true
로 간주됩니다.
주목
:
기호를 사용하면 전체 표현식이 인용된 문자열 값이 아닌 경우 yaml 구문과 충돌할 수 있습니다.
-or
및 -and
연산자를 사용하면 비교 연산자를 결합하여 더 복잡한 조건을 구성할 수 있습니다.
주목:
보다 전통적인 연산자 기호 ||
(그리고 &&
)는 여기서 사용할 수 없습니다. 왜냐하면 연산자 ||
논리 연산에 적용되지 않는 다른 의미를 가진 dynaml에 이미 존재합니다. false || true
false
로 평가됩니다. 정의된 경우 해당 값에 관계없이 첫 번째 피연산자를 생성하기 때문입니다. 가능한 한 호환성을 유지하기 위해 이를 변경할 수 없으며 단순 기호 or
및 and
사용할 수 없습니다. 이렇게 하면 해당 이름과의 참조 연결이 무효화되기 때문입니다.
(( 5 -or 6 ))
-or
또는 -and
연산자의 양쪽이 모두 정수 값으로 평가되면 비트 단위 연산이 실행되고 결과는 다시 정수가 됩니다. 따라서 표현식 5 -or 6
은 7
로 평가됩니다.
Dynaml은 사전 정의된 함수 세트를 지원합니다. 함수는 일반적으로 다음과 같이 호출됩니다.
result : (( functionname(arg, arg, ...) ))
람다 표현식을 사용하여 추가 함수를 yaml 문서의 일부로 정의할 수 있습니다. 그러면 함수 이름은 그룹화된 표현식이거나 람다 표현식을 호스팅하는 노드에 대한 경로입니다.
(( format( "%s %d", alice, 25) ))
dynaml 표현식으로 제공된 인수를 기반으로 문자열 형식을 지정합니다. 이 함수에는 두 번째 특징이 있습니다. error
오류 메시지의 형식을 지정하고 평가를 실패로 설정합니다.
(( join( ", ", list) ))
지정된 구분 문자열을 사용하여 목록 항목을 결합하거나 값을 단일 문자열 값으로 연결합니다. 조인에 대한 인수는 목록으로 평가되는 동적 표현식일 수 있으며, 해당 값은 다시 문자열이나 정수, 문자열이나 정수 값입니다.
예:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
join
에 대해 문자열 값 bob, foo, bar, alice, 10
생성합니다.
(( split( ",", string) ))
전용 구분 기호로 문자열을 분할합니다. 결과는 목록입니다. 구분자 문자열 대신 정수 값이 주어질 수 있으며, 이는 주어진 문자열을 길이가 제한된 문자열 목록으로 분할합니다. 길이는 바이트가 아닌 룬 단위로 계산됩니다.
예:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
수익률:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
선택적으로 세 번째 인수를 지정할 수 있습니다. 반환되는 목록 항목의 수를 제한합니다. 값 -1을 사용하면 목록 길이가 무제한이 됩니다.
정규식을 구분자 문자열로 사용해야 하는 경우 split_match
함수를 사용할 수 있습니다.
(( trim(string) ))
문자열 또는 문자열 목록의 모든 요소를 자릅니다. 선택적인 두 번째 문자열 인수가 있습니다. 잘릴 문자 세트를 지정하는 데 사용할 수 있습니다. 기본 컷 세트는 공백과 탭 문자로 구성됩니다.
예:
list : (( trim(split("," "alice, bob")) ))
수익률:
list :
- alice
- bob
(( element(list, index) ))
인덱스가 제공하는 전용 목록 요소를 반환합니다.
예:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
수익률:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
해당 키로 제공된 전용 맵 필드를 반환합니다.
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
수익률:
map :
alice : 24
bob : 25
elem : 25
이 함수는 점(.)이 포함된 키도 처리할 수 있습니다.
(( compact(list) ))
빈 항목을 생략하고 목록을 필터링합니다.
예:
list : (( compact(trim(split("," "alice, , bob"))) ))
수익률:
list :
- alice
- bob
(( uniq(list) ))
Uniq는 중복되지 않은 목록을 제공합니다.
예:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
필드 uniq
의 수율:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
목록에 전용 값이 포함되어 있는지 확인합니다. 값은 목록이나 맵일 수도 있습니다.
예:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
수익률:
list :
- foo
- bar
- foobar
contains : true
contains
함수는 문자열에 대해 작동하여 하위 문자열을 찾거나 맵을 검색하여 키를 찾습니다. 이러한 경우 요소는 문자열이어야 합니다.
예:
contains : (( contains("foobar", "bar") ))
결과는 true
.
(( basename(path) ))
basename
함수는 경로의 마지막 요소 이름을 반환합니다. 인수는 일반 경로 이름이거나 URL일 수 있습니다.
예:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
수익률:
pathbase : bob
urlbase : bob
(( dirname(path) ))
dirname
함수는 경로의 상위 디렉터리를 반환합니다. 인수는 일반 경로 이름이거나 URL일 수 있습니다.
예:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
수익률:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
이 함수는 URL을 구문 분석하고 URL의 모든 요소가 포함된 지도를 생성합니다. 필드 port
, userinfo
및 password
는 선택 사항입니다.
예 : :
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
수확량 :
url :
scheme : https
host : github.com
port : 443
path : /mandelsoft/spiff
fragment : anchor
query : branch=master&tag=v1
values :
branch : [ master ]
tag : [ v1 ]
userinfo :
username : user
password : pass
(( index(list, "foobar") ))
목록에 전용 값이 포함되어 있는지 확인하고 첫 번째 일치의 색인을 반환합니다. 값은 목록 또는지도 일 수도 있습니다. 항목을 찾을 수없는 경우 -1
이 반환됩니다.
예 : :
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
수확량 :
list :
- foo
- bar
- foobar
index : 2
기능 index
또한 문자열에서 작동하여 하위 문자열을 찾습니다.
예 : :
index : (( index("foobar", "bar") ))
3. 3
.
(( lastindex(list, "foobar") ))
기능 lastindex
기능은 index
처럼 작동하지만 마지막 발생의 인덱스가 반환됩니다.
함수 sort
정수 또는 문자열 목록을 정렬하는 데 사용할 수 있습니다. 정렬 작업은 안정적입니다.
예 : :
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
sorted
수율
- alice
- bob
- foobar
다른 유형을 정렬 해야하는 경우, 특히 목록이나 맵과 같은 복잡한 유형 또는 다른 비교 규칙이 필요하면 비교 함수를 선택적 두 번째 인수로 지정할 수 있습니다. 비교 함수는 두 가지 인수를 취하는 람다 표현이어야합니다. 결과 유형은 a가 b 이하인지 여부를 나타내는 integer
또는 bool
이어야합니다. 정수가 반환되면 그럴 것입니다
예 : :
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
sorted
수율
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
교체 문자열로 문자열에서 하위 문자열의 모든 발생을 교체하십시오. 선택적 네 번째 정수 인수를 사용하면 대체 수가 제한 될 수 있습니다 (-1 평균 무제한).
예 : :
string : (( replace("foobar", "o", "u") ))
fuubar
생산합니다.
정규 표현식을 검색 문자열로 사용해야하는 경우 replace_match
함수를 사용할 수 있습니다. 여기서 검색 문자열은 정규 표현식으로 평가됩니다. 하위 표현식이있을 수 있습니다. 이 일치는 교체 문자열에 사용할 수 있습니다
예 : :
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
fbooar
생산합니다.
대체 인수는 Lambda 기능 일 수도 있습니다. 이 경우 모든 일치마다 기능이 호출되어 교체 값을 결정합니다. 단일 입력 인수는 실제 하위 표현식 일치 목록입니다.
예 : :
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
fOObar-barfoo
산출합니다.
(( substr(string, 1, 2) ))
주어진 시작 인덱스에서 옵션 엔드 인덱스 (독점)까지 시작하여 문자열에서 스텁 문자열을 추출하십시오. 끝 인덱스가 주어지지 않으면 문자열의 끝까지 하위 struvt가 추출됩니다. 두 지수 모두 음수 일 수 있습니다. 이 경우 문자열 끝에서 가져옵니다.
예 : :
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
평가합니다
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
주어진 문자열 값에 대한 정규 표현식의 일치를 반환합니다. 경기는 정규 표현식에 포함 된 서브 표현식의 일치 값 목록입니다. 인덱스 0은 완전한 정규 표현식의 일치를 나타냅니다. 문자열 값이 일치하지 않으면 빈 목록이 반환됩니다.
예 : :
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
수확량 :
matches :
- foobar
- foo
- bar
유형 정수의 세 번째 인수는 최대 N 반복의 멀티 일치를 요청하기 위해 주어질 수 있습니다. 값이 음수 인 경우 모든 반복이보고됩니다. 결과는 각각 위에서 설명한 형식의 모든 일치 목록입니다.
(( keys(map) ))
맵에 사용 된 키의 정렬 된 키 목록을 결정하십시오.
예 : :
map :
alice : 25
bob : 25
keys : (( keys(map) ))
수확량 :
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
목록,지도 또는 문자열 값의 길이를 결정하십시오.
예 : :
list :
- alice
- bob
length : (( length(list) ))
수확량 :
list :
- alice
- bob
length : 2
(( base64(string) ))
기능 base64
주어진 문자열의 Base64 인코딩을 생성합니다. base64_decode
Base64 인코딩 된 문자열을 디코딩합니다.
예 : :
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
평가합니다
base54 : dGVzdA==
test : test
선택적 두 번째 인수는 최대 선 길이를 지정하는 데 사용될 수 있습니다. 이 경우 결과는 멀티 라인 문자열입니다.
(( hash(string) ))
함수 hash
주어진 문자열에 대해 여러 종류의 해시를 생성합니다. sha256
해시가 생성 된 기본적으로 기본적으로. 선택적 두 번째 인수는 해시 유형을 지정합니다. 가능한 유형은 md4
, md5
, sha1
, sha224
, sha256
, sha384
, sha2512
, sha512/224
또는 sha512/256
입니다.
md5
해시는 여전히 더 이상 사용되지 않은 Finctio md5(string)
에 의해 생성 될 수 있습니다.
예 : :
data : alice
hash :
deprecated : (( md5(data) ))
md4 : (( hash(data,"md4") ))
md5 : (( hash(data,"md5") ))
sha1 : (( hash(data,"sha1") ))
sha224 : (( hash(data,"sha224") ))
sha256 : (( hash(data,"sha256") ))
sha384 : (( hash(data,"sha384") ))
sha512 : (( hash(data,"sha512") ))
sha512_224 : (( hash(data,"sha512/224") ))
sha512_256 : (( hash(data,"sha512/256") ))
평가합니다
data : alice
hash :
deprecated : 6384e2b2184bcbf58eccf10ca7a6563c
md4 : 616c69636531d6cfe0d16ae931b73c59d7e0c089c0
md5 : 6384e2b2184bcbf58eccf10ca7a6563c
sha1 : 522b276a356bdf39013dfabea2cd43e141ecc9e8
sha224 : 38b7e5d5651aaf85694a7a7c6d5db1275af86a6df93a36b8a4a2e771
sha256 : 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90
sha384 : 96a5353e625adc003a01bdcd9b21b21189bdd9806851829f45b81d3dfc6721ee21f6e0e98c4dd63bc559f66c7a74233a
sha512 : 408b27d3097eea5a46bf2ab6433a7234a33d5e49957b13ec7acc2ca08e1a13c75272c90c8d3385d47ede5420a7a9623aad817d9f8a70bd100a0acea7400daa59
sha512_224 : c3b8cfaa37ae15922adf3d21606e3a9836ba2a9d7838b040b7c96fd7
sha512_256 : ad0a339b08dc090fe3b16eae376f7e162836e8728da9c45466842e19508d7627
(( bcrypt("password", 10) ))
함수 bcrypt
지정된 비용 계수 (누락 된 경우 기본적으로 10으로 기본값)를 사용하여 주어진 문자열에 대해 bcrypt 암호 해시를 생성합니다.
예 : :
hash : (( bcrypt("password", 10) ))
평가합니다
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
함수 bcrypt_check
주어진 bcrypt 해시에 대한 비밀번호를 확인합니다.
예 : :
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
평가합니다
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
md5crypt
함수는 주어진 문자열에 대해 Apache MD5 암호화 암호 해시를 생성합니다.
예 : :
hash : (( md5crypt("password") ))
평가합니다
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
md5crypt_check
함수는 주어진 Apache MD5 암호화 된 해시에 대한 비밀번호를 확인합니다.
예 : :
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
평가합니다
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
이 기능은 암호화 된 비밀을 Spiff Yaml 파일에 저장하는 데 사용될 수 있습니다. 처리 된 결과에는 해독 된 값이 포함됩니다. 모든 노드 유형은 완전한 맵 및 목록을 포함하여 암호화 및 해독 할 수 있습니다.
암호 해독에 대한 비밀번호는 두 번째 인수로 주어질 수 있거나 (선호하는 방법) 환경 변수 SPIFF_ENCRYPTION_KEY
로 지정할 수 있습니다.
선택적 마지막 인수는 암호화 방법을 선택할 수 있습니다. 지금까지 지원되는 유일한 방법은 3DES
입니다. SPIFF 라이브러리에서 제공하는 암호화 메소드 등록을 사용하여 전용 SPIFF 버전에 다른 방법이 추가 될 수 있습니다.
encrypt("secret")
함수를 사용하여 값을 암호화 할 수 있습니다.
예 : :
password : this a very secret secret and may never be exposed to unauthorized people
encrypted : (( encrypt("spiff is a cool tool", password) ))
decrypted : (( decrypt(encrypted, password) ))
같은 것으로 평가했습니다
decrypted : spiff is a cool tool
encrypted : d889f9e4cc7ae13effcbc8bb8cd0c38d1fb2197738444f753c48796d7946083e6639e5a1bf8f77648f2a1ddf37023c65ff57d52d0519d1d92cbcf87d3e263cba
password : this a very secret secret and may never be exposed to unauthorized people
(( rand("[:alnum:]", 10) ))
함수 rand
임의의 값을 생성합니다. 첫 번째 인수는 어떤 종류의 값이 요청되는지 결정합니다. 인수가 없으면 int64
범위에서 양의 랜덤 숫자를 생성합니다.
인수 유형 | 결과 |
---|---|
정수 | 양의 N 의 경우 [0, N ) 범위의 정수 값 및 음수 N 의 경우 ( N , 0] |
부울 | 부울 가치 |
끈 | 룬이 주어진 문자 범위에있는 하나의 룬 줄, Regexp에 사용할 수있는 캐릭터 클래스 또는 캐릭터 범위의 조합을 사용할 수 있습니다. 추가 길이 인수가 지정되면 결과 문자열은 주어진 길이를 갖습니다. |
예 : :
int : (( rand() ))
int10 : (( rand(10) ))
neg10 : (( rand(-10) ))
bool : (( rand(true) ))
string : (( rand("[:alpha:][:digit:]-", 10) ))
upper : (( rand("A-Z", 10) ))
punct : (( rand("[:punct:]", 10) ))
alnum : (( rand("[:alnum:]", 10) ))
평가합니다
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
함수 type
주어진 표현식의 유형을 나타내는 문자열을 생성합니다.
예 : :
template :
<< : (( &template ))
types :
- int : (( type(1) ))
- float : (( type(1.0) ))
- bool : (( type(true) ))
- string : (( type("foobar") ))
- list : (( type([]) ))
- map : (( type({}) ))
- lambda : (( type(|x|->x) ))
- template : (( type(.template) ))
- nil : (( type(~) ))
- undef : (( type(~~) ))
유형을 평가합니다
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
함수는 표현식이 성공적으로 평가 될 수 있는지 defined
를 확인합니다. 표현식을 평가할 수 있다면 부울 값이 true
생성하고 그렇지 않으면 false
산출합니다.
예 : :
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
평가합니다
zero : 0
div_ok : false
zero_def : true
null_def : false
이 기능은 조건부 연산자의 조합으로 사용하여 다른 표현의 해결 가능성에 따라 표현식을 평가할 수 있습니다.
(( valid(foobar) ))
기능 valid
기능은 표현식이 성공적으로 평가 될 수 있는지 확인하고 nil
과 동일하지 않은 정의 값으로 평가합니다. 표현식을 평가할 수 있다면 부울 값이 true
생성하고 그렇지 않으면 false
산출합니다.
예 : :
zero : 0
empty :
map : {}
list : []
div_ok : (( valid(1 / zero ) ))
zero_def : (( valid( zero ) ))
null_def : (( valid( ~ ) ))
empty_def : (( valid( empty ) ))
map_def : (( valid( map ) ))
list_def : (( valid( list ) ))
평가합니다
zero : 0
empty : null
map : {}
list : []
div_ok : false
zero_def : true
null_def : false
empty_def : false
map_def : true
list_def : true
(( require(foobar) ))
주어진 인수가 정의되지 않거나 nil
인 경우 함수는 오류를 require
. 그렇지 않으면 주어진 값을 산출합니다.
예 : :
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
평가합니다
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
함수 stub
이를 정의하는 첫 번째 상류 스텁에서 발견되는 전용 필드의 값을 생성합니다.
예 : :
template.yml
value : (( stub(foo.bar) ))
스터브와 병합
stub.yml
foo :
bar : foobar
평가합니다
value : foobar
이 함수에 전달 된 인수는 참조 문자 또는 참조를 나타내는 문자열 또는 참조 경로 요소 목록을 나타내는 문자열 목록으로 평가하는 표현식이어야합니다. 인수 나 정의되지 않은 ( ~~
)가 제공되지 않으면 실제 필드 경로가 사용됩니다.
주어진 유일한 참조는 표현으로 평가되지 않으며, 그 값을 사용해야하는 경우, 예를 들어 목록 표현식에 대한 (ref)
또는 [] ref
표시하여 표현으로 변환해야합니다.
또는 merge
작업 (예를 들어 merge foo.bar
작업을 사용할 수 있습니다. 차이점은 stub
병합되지 않으므로 필드가 여전히 병합됩니다 (문서의 원래 경로와 함께).
(( tagdef("tag", value) ))
함수 tagdef
동적 태그를 정의하는 데 사용될 수 있습니다 (태그 참조). 태그 마커와 달리이 함수는 표현식으로 태그 이름과 의도 된 값을 지정할 수 있습니다. 따라서 map
또는 sum
같은 요소를 계산하여 계산 된 값으로 동적 태그를 작성하는 데 사용될 수 있습니다.
선택적 세 번째 인수는 의도 된 범위 ( local
또는 global
)를 지정하는 데 사용될 수 있습니다. 기본적으로 로컬 태그가 생성됩니다. 로컬 태그는 실제 처리 수준 (템플릿 또는 서브)에서만 볼 수 있으며, 한 번 정의 된 글로벌 태그는 모든 추가 처리 수준 (스터브 또는 템플릿)에서 사용할 수 있습니다.
또는 태그 이름을 시작 ( *
)로 접두사로 만들어 글로벌 태그를 선언 할 수 있습니다.
지정된 태그 값은 함수의 결과로 사용됩니다.
예 : :
template.yml
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
평가합니다
value : 25
alice : 25
(( eval(foo "." bar ) ))
문자열 표현식의 평가 결과를 다시 Dynaml 표현으로 평가합니다. 예를 들어, 간접을 실현하는 데 사용될 수 있습니다.
EG : 표현
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
필드로가는 경로를 계산 한 다음이 구성된 필드의 값을 산출하기 위해 다시 평가됩니다.
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
이름이 Dynaml 표현식으로 제공되는 환경 변수의 값을 읽으십시오. 환경 변수가 설정되지 않으면 평가가 실패합니다.
두 번째 맛에서 함수 env
단일 목록에 결합 된 여러 인수 및/또는 목록 인수를 받아들입니다. 이 목록의 모든 항목은 환경 변수의 이름으로 사용되며 함수의 결과는 주어진 변수의 맵입니다. 이에 따라 존재하지 않는 환경 변수는 생략됩니다.
(( parse(yamlorjson) ))
YAML 또는 JSON 문자열을 구문 분석하고 컨텐츠를 YAML 값으로 반환하십시오. 따라서 추가적인 Dynaml 평가에 사용될 수 있습니다.
예 : :
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
필드 result
에 대한 값 25
생성합니다.
함수 parse
선택적 두 번째 인수 인 구문 분석 모드를 지원합니다. 여기서 읽기 함수와 동일한 모드가 가능합니다. 기본 구문 분석 모드는 import
이고 콘텐츠는 방금 구문 분석 되며이 단계에서는 더 이상 평가가 없습니다.
(( asjson(expr) ))
이 함수는 인수에 의해 주어진 Yaml 값을 JSON 문자열로 변환합니다. 해당 함수 asyaml
Yaml 값을 Yaml 문서 문자열로 생성합니다.
예 : :
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
해결합니다
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
이 기능은 표현식을 실행하고 평가 정보 맵을 생성합니다. 표현이 실패하더라도 항상 성공합니다. 지도에는 다음 필드가 포함되어 있습니다.
이름 | 유형 | 의미 |
---|---|---|
valid | 부울 | 표현이 유효합니다 |
error | 끈 | 평가의 오류 메시지 텍스트 |
value | 어느 | 평가가 성공한 경우 표현의 가치 |
예 : :
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
해결합니다
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
작업용 정적 IP 목록을 생성하십시오.
예 : :
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
이렇게하면 mynetwork
S 서브넷에서 3 개의 IP가 생성되고 두 개의 인스턴스 만 있으므로 2 개의 항목을 반환합니다. 두 항목은 네트워크에서 정의 된 정적 IP 범위의 0 번째 및 3 번째 오프셋입니다.
예를 들어, bye.yml 파일이 주어지면 :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
그리고 파일 hi.yml :
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
spiff merge bye.yml hi.yml
보고
jobs :
- instances : 3
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
- 10.60.3.70
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
.
Bye.yml 이 대신 인 경우
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
대신 돌아옵니다
jobs :
- instances : 2
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
static_ips
또한 모든 포함 된 요소가 다시 목록 또는 정수 값인 경우 목록 인수도 허용합니다. 이를 통해 IP 목록을 다음과 같이 할인 할 수 있습니다.
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
역사적 이유로 STATIC_IPS 기능은 BOSH 매니페스트의 구조에 의존하고 매니페스트의 전용 위치에서만 작동하지만 IPSET 기능은 인수를 기반으로 순전히 유사한 계산을 제공합니다. 따라서 사용 가능한 IP 범위와 필요한 수의 IP 수는 인수로 전달됩니다.
첫 번째 (범위) 인수는 간단한 문자열 또는 문자열 목록으로 단일 범위 일 수 있습니다. 모든 문자열이있을 수 있습니다
두 번째 인수는 결과 세트에서 요청 된 IP 주소 수를 지정합니다.
추가 인수는 주어진 범위에서 선택할 IP의 지수를 지정합니다 (0부터 시작). 여기에 다시 인덱스 목록이 사용될 수 있습니다.
예 : :
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
ipset을 [ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
로 해결합니다.
IP 지수가 지정되지 않은 경우 (두 개의 인수 만) IPS는 첫 번째 범위의 시작부터 마지막 주어진 범위의 끝까지 시작하여 간접없이 선택됩니다.
(( list_to_map(list, "key") ))
명시 적 이름/키 필드가있는 맵 항목 목록은 전용 키가있는 맵에 매핑됩니다. 기본적으로 키 필드 name
사용되며, 이는 선택적 두 번째 인수에 의해 변경 될 수 있습니다. 목록의 명시 적으로 표시된 키 필드도 고려됩니다.
예 : :
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
매핑됩니다
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
템플릿 및 람다 표현식과 함께 이는 핵심 값에 대해서는 Dynaml 표현식이 허용되지 않지만 임의로 명명 된 핵심 값을 가진 맵을 생성하는 데 사용할 수 있습니다.
(( makemap(fieldlist) ))
이 맛에서 makemap
주어진 필드 목록에 설명 된 항목이 포함 된 맵을 만듭니다. 목록에는 전용 맵 항목을 설명하는 항목 key
및 value
이 포함 된 맵이 포함되어 있어야합니다.
예 : :
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
수율
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
키 값이 부울 또는 정수 인 경우 문자열에 매핑됩니다.
(( makemap(key, value) ))
이 맛에서 makemap
주어진 인수 쌍에 의해 설명 된 항목이있는 맵을 만듭니다. 인수는 열쇠/값 쌍의 시퀀스 일 수 있습니다 (별도의 인수에 의해 제공됨).
예 : :
map : (( makemap("peter", 23, "paul", 22) ))
수율
map :
paul : 22
peter : 23
이전 makemap
풍미와 달리, 이것은 맵 리터럴에 의해 처리 될 수 있습니다.
(( merge(map1, map2) ))
키워드 merge
외에도 merge
라는 기능도 있습니다 (항상 오프닝 브래킷을 따라야합니다). 스터브 병합 프로세스와 유사한 실제 문서에서 가져온 악화 맵을 병합하는 데 사용할 수 있습니다. 맵이 참조 표현식으로 지정된 경우, 인수를 평가하기 전에 실제 문서의 맥락에서 항상 평가되기 때문에 Dynaml 표현식을 포함 할 수 없습니다.
예 : :
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
result
를 해결합니다
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
또는 맵 템플릿을 전달할 수 있습니다 (평가 연산자없이!). 이 경우 템플릿의 Dynaml 표현식은 Spiff 병합 의 정기적 인 호출과 같이 주어진 문서를 병합하는 동안 평가됩니다.
예 : :
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
result
를 해결합니다
result :
alice : 26
bob : 26
맵 표현식에 의해지도가 주어질 수도 있습니다. 여기서 일반적인 구문을 사용하여 Dynaml 표현식을 지정할 수 있습니다.
예 : :
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
result
를 해결합니다
result :
alice : 26
bob : 100
여러 인수 대신 단일 목록 인수가 제공 될 수 있습니다. 목록에는 병합 될지도가 포함되어야합니다.
중첩 합병은 모든 외부 바인딩에 액세스 할 수 있습니다. 상대 참조는 실제 문서에서 먼저 검색됩니다. 그것들이 발견되지 않으면 모든 외부 바인딩은 내부에서 외부 바인딩으로 참조를 조회하는 데 사용됩니다. 또한 Context ( __ctx
)는 Field OUTER
제공하며, 이는 중첩 합병의 모든 외부 문서 목록이며 절대 참조를 조회하는 데 사용할 수 있습니다.
예 : :
data :
alice :
age : 24
template :
<< : (( &template ))
bob : 25
outer1 : (( __ctx.OUTER.[0].data )) # absolute access to outer context
outer2 : (( data.alice.age )) # relative access to outer binding
sum : (( .bob + .outer2 ))
merged : (( merge(template) ))
merged
해결
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
함수는 intersect
여러 목록을 교차시킵니다. 목록에는 모든 유형의 항목이 포함될 수 있습니다.
예 : :
list1 :
- - a
- - b
- a
- b
- { a: b }
- { b: c }
- 0
- 1
- " 0 "
- " 1 "
list2 :
- - a
- - c
- a
- c
- { a: b }
- { b: b }
- 0
- 2
- " 0 "
- " 2 "
intersect : (( intersect(list1, list2) ))
intersect
해결됩니다
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
함수 reverse
목록의 순서를 되돌립니다. 목록에는 모든 유형의 항목이 포함될 수 있습니다.
예 : :
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
reverse
로 해결됩니다
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
이 함수 validate
유효성 검사기 세트를 사용하여 표현식을 검증합니다. 첫 번째 인수는 검증 할 가치이며 다른 모든 인수는 값을 수용하기 위해 성공 해야하는 유효성 검사기입니다. 적어도 하나의 유효성 검사기가 실패하면 실패 이유를 설명하는 적절한 오류 메시지가 생성됩니다.
유효성 검사기는 문자열 또는 인수로 유효성 검사기 유형을 포함하는 문자열 또는 목록으로 표시됩니다. 유효성 검사기는 선행으로 부정 할 수 있습니다 !
이름으로.
다음 유효성 검사기를 사용할 수 있습니다.
유형 | 논쟁 | 의미 |
---|---|---|
empty | 없음 | 빈 목록,지도 또는 문자열 |
dnsdomain | 없음 | DNS 도메인 이름 |
wildcarddnsdomain | 없음 | 와일드 카드 DNS 도메인 이름 |
dnslabel | 없음 | DNS 레이블 |
dnsname | 없음 | DNS 도메인 또는 와일드 카드 도메인 |
ip | 없음 | IP 주소 |
cidr | 없음 | 시드르 |
publickey | 없음 | PEM 형식의 공개 키 |
privatekey | 없음 | PEM 형식의 개인 키 |
certificate | 없음 | PEM 형식의 인증서 |
ca | 없음 | CA 인증서 |
semver | 제약 조건의 선택적 목록 | 제약 조건에 대해 Semver 버전을 검증하십시오 |
type | 허용 된 유형 키 목록 | 적어도 하나의 유형 키가 일치해야합니다 |
valueset | 인수를 값으로 나열하십시오 | 가능한 값 |
value 또는 = | 값 | 전용 값을 확인하십시오 |
gt 또는 > | 값 | 보다 큽니다 (번호/문자열) |
lt 또는 < | 값 | 덜 (번호/문자열) |
ge 또는 >= | 값 | 크거나 동일 (번호/문자열) |
le 또는 <= | 값 | 덜 또는 동등한 (번호/문자열) |
match 또는 ~= | 정규식 | 정규 표현식 일치 문자열 값 |
list | 입력 유효성 검사기 선택 목록 | 주어진 유효성 검사기와 일치하는 목록 및 항목 |
map | [[<key validator>,] <입력 유효성 검사기]] | MAP 및 키 및 항목은 주어진 유효성 검사기 일치합니다 |
mapfield | <필드 이름> [, <유지자>] | 맵에서 필요한 항목 |
optionalfield | <필드 이름> [, <유지자>] | 맵의 선택적 항목 |
and | 유효성 검사기 목록 | 모든 유효성 검사기는 성공해야합니다 |
or | 유효성 검사기 목록 | 적어도 하나의 유효성 검사기가 성공해야합니다 |
not ! | 검증인 | 유효성 검사 인수를 부정 |
유효성 검사가 성공하면 값이 반환됩니다.
예 : :
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
평가합니다
dnstarget : 192.168.42.42
유효성 검사에 실패하면 실패 이유를 설명하는 오류가 발생합니다.
예 : :
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
다음 오류가 발생합니다.
*condition 1 failed: (is no ip address: alice+bob and is no dns domain: [a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')])
유효성 검사기는 또한 적어도 하나의 논쟁을 취하고 부울 가치를 반환하는 람다 표현 일 수도 있습니다. 이렇게하면 YAML 문서의 일부로 자체 유효성 검사기를 제공 할 수 있습니다.
예 : :
val : (( validate( 0, |x|-> x > 1 ) ))
둘 이상의 매개 변수가 선언되면 추가 인수는 유효성 검사기 인수로 지정되어야합니다. 첫 번째 인수는 항상 확인해야 할 가치입니다.
예 : :
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
Lambda 함수는 1, 2 또는 3 요소의 목록을 반환 할 수도 있습니다. 이것은 적절한 메시지를 제공하는 데 사용할 수 있습니다.
색인 | 의미 |
---|---|
0 | 첫 번째 색인은 항상 매치 결과이며 부울으로 평가할 수 있어야합니다. |
1 | 두 요소가 주어지면 두 번째 색인은 실제 결과를 설명하는 메시지입니다. |
2 | 여기서 인덱스 1은 성공 메시지와 2 실패 메시지를 분류합니다. |
예 : :
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
언급하면, 유효성 검사기 사양은 위의 예와 같이 인라인으로 제공 될 수 있지만 참조 표현식으로도 제공 될 수 있습니다. not
and
또는 or
유효성 검사기는 깊이 중첩 된 유효성 검사기 사양을 허용합니다.
예 : :
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
함수 check
YAML 기반 값 검사기와 YAML 구조를 일치시키는 데 사용될 수 있습니다. 이에 따라 유효성 검사에 대해 이미 설명한 동일한 확인 설명이 사용할 수 있습니다. 통화 결과는 일치 결과를 나타내는 부울 값입니다. 수표가 실패하면 실패하지 않습니다.
(( error("message") ))
기능 error
전용 메시지로 명시 적 평가 실패를 일으키는 데 사용될 수 있습니다.
예를 들어, 오류 함수를 잠재적으로 실패한 불만 표현식에 대한 기본값으로 추가하여 복잡한 처리 오류를 의미있는 메시지로 줄이는 데 사용할 수 있습니다.
예 : :
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
또 다른 시나리오는 상류 스터브에 정의 된 필드의 오류 표현식을 (기본) 값으로 사용하여 필요한 필드에 대한 설명 메시지를 생략하는 것일 수 있습니다.
Dynaml은 다양한 수학 기능을 지원합니다.
반환 정수 : ceil
, floor
, round
및 roundtoeven
반환 수레 또는 정수 : abs
귀환 수레 : sin
, cos
, sinh
, cosh
, asin
, acos
, asinh
, acosh
, sqrt
, exp
, log
, log10
,
Dynaml은 적절한 함수에 의해 integer
, float
, bool
및 string
값 사이의 다양한 유형 변환을 지원합니다.
예 : :
value : (( integer("5") ))
문자열을 정수 값으로 변환합니다.
정수를 문자열로 변환하면 변환을위한 기반을 지정하기위한 선택적 추가 정수 인수가 수용되면 string(55,2)
이 "110111"
이 발생합니다. 기본베이스는 10입니다.베이스는 2에서 36 사이 여야합니다.
Spiff는 템플릿 및 하위 파일 외부의 컨텐츠에 대한 액세스를 지원합니다. 파일을 읽고 명령 및 파이프 라인을 실행할 수 있습니다. 이러한 모든 기능은 두 가지 맛으로 존재합니다.
sync
함수가 사용되는 경우 템플릿 처리를 전용 상태 (외부 컨텐츠에 의해 제공)와 동기화하기위한 경우입니다. 여기서 캐싱 작업은 유용하지 않으므로 두 번째로 성분되지 않은 맛이 있습니다. 모든 기능은 접미사 _uncached
(예 : read_uncached()
)와 함께 사용할 수 있습니다. (( read("file.yml") ))
파일을 읽고 내용을 반환하십시오. yaml
파일, text
파일 및 binary
파일의 세 가지 콘텐츠 유형에 대한 지원이 있습니다. 이진 모드로의 읽기는 Base64 인코딩 된 멀티 라인 문자열을 초래합니다.
파일 접미사가 .yml
, .yaml
또는 .json
인 경우 기본적으로 Yaml 유형이 사용됩니다. 파일을 text
로 읽으려면이 유형을 명시 적으로 지정해야합니다. 다른 모든 경우에서는 기본값이 text
이므로 바이너리 파일 (예 : 아카이브)을 시급히 읽으려면 binary
모드를 지정해야합니다.
옵션 두 번째 매개 변수를 사용하여 원하는 리턴 유형 ( yaml
또는 text
을 명시 적으로 지정할 수 있습니다. YAML 문서의 경우 multiyaml
, template
, templates
, import
importmulti
일부 추가 유형이 지원됩니다.
YAML 문서가 구문 분석되고 트리가 반환됩니다. 트리의 요소는 정기적 인 Dynaml 표현식으로 액세스 할 수 있습니다.
또한 Yaml 파일에는 다시 Dynaml 표현식이 포함될 수 있습니다. 포함 된 모든 Dynaml 표현식은 읽기 표현의 맥락에서 평가됩니다. 이는 YAML 문서의 다른 장소에 포함 된 동일한 파일이 사용 된 Dynaml 표현식에 따라 다른 서브 트리를 초래할 수 있음을 의미합니다.
다중 문서 Yaml을 읽을 수있는 경우. multiyaml
유형이 주어지면 Yaml 문서 루트 노드가있는 목록 노드가 반환됩니다.
YAML 또는 JSON 문서는 유형 template
지정하여 템플릿 으로 읽을 수도 있습니다. 여기서 결과는 일반 인라인 템플릿처럼 사용할 수있는 템플릿 값입니다. templates
지정되면 멀티 문서가 템플릿 목록에 매핑됩니다.
읽기 유형이 import
설정되면 파일 내용은 Yaml 문서로 읽히고 루트 노드는 표현식을 대체하는 데 사용됩니다. 문서에 포함 된 잠재적 인 Dynaml 표현식은 읽기 호출과 함께 표현식의 실제 바인딩으로 평가되지 않고 원본 파일의 일부가되었을 것입니다. 따라서이 모드는 읽기 결과의 추가 처리가 없거나 전달되지 않은 값이 처리되지 않은 경우에만 사용할 수 있습니다.
이것은 수입 된 문서의 전용 조각을 지키기 위해 Chained Reference (시험 (( read(...).selection ))
)와 함께 사용할 수 있습니다. 그런 다음 선택된 부분에 대해서만 평가가 수행됩니다. 다른 부분의 표현 및 참조는 평가되지 않았으며 전혀 오류로 이어질 수 없습니다.
예 : :
템플릿 .yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
import.yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
second
섹션은 절대로 평가되지 않기 때문에 실패하지 않습니다.
이 모드는 종종 예상치 못한 결과로 이어지기 때문에주의해서 사용해야합니다.
읽기 유형 importmulti
멀티 문서 YAML 파일을 노드 목록으로 가져 오는 데 사용될 수 있습니다.
텍스트 문서는 단일 문자열로 반환됩니다.
이진 문서를 읽을 수도 있습니다. 내용은 문자열 (또는 Yaml 문서)으로 직접 사용할 수 없습니다. 따라서 읽기 모드 binary
지정해야합니다. 컨텐츠는 Base64 인코딩 된 멀티 라인 문자열 값으로 반환됩니다.
(( exec("command", arg1, arg2) ))
명령을 실행하십시오. 인수는 목록 또는지도에 대해 평가 된 참조 표현식을 포함한 모든 Dynaml 표현식 일 수 있습니다. 목록 또는지도는 주어진 조각이있는 YAML 문서를 포함하는 단일 인수로 전달됩니다.
결과는 명령의 표준 출력을 구문 분석하여 결정됩니다. YAML 문서 또는 단일 멀티 라인 문자열 또는 정수 값일 수 있습니다. YAML 문서는 문서 접두사로 시작해야합니다 ---
. 명령이 실패하면 표현식은 정의되지 않은 것으로 처리됩니다.
예를 들어
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
수율
arg :
- a
- b
list :
- a
- b
string : a
또는 명령 줄을 완전히 설명하는 단일 목록 인수와 함께 exec
호출 할 수 있습니다.
동일한 명령은 여러 표현식으로 사용되는 경우에도 한 번만 실행됩니다.
(( pipe(data, "command", arg1, arg2) ))
명령을 실행하고 전용 데이터로 표준 입력을 공급하십시오. 명령 인수는 문자열이어야합니다. 명령에 대한 인수는 목록 또는지도에 대해 평가 된 참조 표현식을 포함한 모든 Dynaml 표현식 일 수 있습니다. 목록 또는지도는 주어진 조각이있는 YAML 문서를 포함하는 단일 인수로 전달됩니다.
입력 스트림은 주어진 데이터에서 생성됩니다. 이것이 간단한 유형 인 경우 문자열 표현이 사용됩니다. 그렇지 않으면 YAML 문서가 입력 데이터에서 생성됩니다. 결과는 명령의 표준 출력을 구문 분석하여 결정됩니다. YAML 문서 또는 단일 멀티 라인 문자열 또는 정수 값일 수 있습니다. YAML 문서는 문서 접두사로 시작해야합니다 ---
. 명령이 실패하면 표현식은 정의되지 않은 것으로 처리됩니다.
예를 들어
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
수율
arg :
- a
- b
list :
- z
- b
또는 pipe
데이터와 명령 줄을 완전히 설명하는 목록 인수와 함께 호출 될 수 있습니다.
동일한 명령은 여러 표현식으로 사용되는 경우에도 한 번만 실행됩니다.
(( write("file.yml", data) ))
파일을 작성하고 콘텐츠를 반환하십시오. 결과를 YAML 문서로 구문 분석 할 수 있으면 문서가 반환됩니다. 옵션 세 번째 인수는 쓰기 옵션을 전달하는 데 사용될 수 있습니다. 옵션 인수는 파일 권한을 나타내는 정수 (기본값이 0644
) 또는 옵션이있는 쉼표로 분리 된 문자열 일 수 있습니다. 지원되는 옵션이 있습니다
binary
: 데이터는 작성하기 전에 Base64가 디코딩됩니다0
은 옥탈 값을 나타냅니다. (( tempfile("file.yml", data) ))
AA 임시 파일을 작성하고 경로 이름을 반환하십시오. 옵션 세 번째 인수는 쓰기 옵션을 통과하는 데 사용될 수 있습니다. 기본적으로 write
처럼 행동합니다
주의 : 임시 파일은 병합 처리 중에 만 존재합니다. 나중에 삭제됩니다.
예를 들어 exec
기능에 대한 임시 파일 인수를 제공하는 데 사용할 수 있습니다.
(( lookup_file("file.yml", list) ))
조회 파일은 디렉토리 목록입니다. 결과는 기존 파일 목록입니다. lookup_dir
사용하면 디렉토리를 조회 할 수 있습니다.
기존 파일을 찾을 수없는 경우 빈 목록이 반환됩니다.
검색 경로를 작성하기 위해 여러 목록 또는 문자열 인수를 전달할 수 있습니다.
(( mkdir("dir", 0755) ))
아직 존재하지 않는 경우 디렉토리와 모든 중간 디렉토리를 만듭니다.
권한 부품은 선택 사항입니다 (기본값 0755). 디렉토리의 경로는 Atring과 같은 값 또는 경로 구성 요소 목록으로 주어질 수 있습니다.
(( list_files(".") ))
디렉터리의 파일을 나열합니다. 결과는 기존 파일 목록입니다. list_dirs
사용하면 디렉토리를 나열 할 수 있습니다.
(( archive(files, "tar") ))
나열된 파일이 포함 된 주어진 유형 (기본값은 tar
)의 아카이브를 만듭니다. 결과는 Base64 인코딩 아카이브입니다.
지원되는 아카이브 유형은 tar
및 targz
입니다.
files
파일 항목의 목록 또는지도 일 수 있습니다. 맵의 경우 맵 키는 파일 경로의 기본값으로 사용됩니다. 파일 항목은 다음 필드가있는 맵입니다.
필드 | 유형 | 의미 |
---|---|---|
path | 끈 | 지도 키에 의해 기본적 인 아카이브의 파일 경로, 맵의 선택 사항 |
mode | int 또는 int String | 파일 모드 또는 쓰기 옵션. 그것은 기본적으로 write 옵션 인수처럼 행동합니다. |
data | 어느 | 파일 컨텐츠, YAML은 YAML 문서로 마샬링됩니다. mode 이진 모드를 나타내는 경우 문자열 값이 Base64 디코딩됩니다. |
base64 | 끈 | Base64 인코딩 된 이진 데이터 |
예 : :
yaml :
alice : 26
bob : 27
files :
" data/a/test.yaml " :
data : (( yaml ))
" data/b/README.md " :
data : |+
### Test Docu
**Note**: This is a test
archive : (( archive(files,"targz") ))
content : (( split("n", exec_uncached("tar", "-tvf", tempfile(archive,"binary"))) ))
수확량 :
archive : |-
H4sIAAAAAAAA/+zVsQqDMBAG4Mx5igO3gHqJSQS3go7tUHyBqIEKitDEoW9f
dLRDh6KlJd/yb8ll+HOd8SY1qbfOJw8zDmQHiIhayjURcZuIQhOeSZlphVwL
glwsAXvM8mJ23twJ4qfnbB/3I+I4pmboW1uA0LSZmgJETr89VXCUtf9Neq1O
5blKxm6PO972X/FN/7nKVej/EaIogto6D+XUzpQydpm8ZayA+tY76B0YWHYD
DV9CEATBf3kGAAD//5NlAmIADAAA
content :
- -rw-r--r-- 0/0 22 2019-03-18 09:01 data/a/test.yaml
- -rw-r--r-- 0/0 41 2019-03-18 09:01 data/b/README.md
files :
data/a/test.yaml :
data :
alice : 26
bob : 27
data/b/README.md :
data : |+
### Test Docu
**Note**: This is a test
yaml :
alice : 26
bob : 27
Spiff는 시맨틱 버전 이름의 처리를 지원합니다. Masterminds Semver 패키지의 모든 기능을 지원합니다 v
(( semver("v1.2-beta.1") ))
주어진 문자열이 의미 론적 버전인지 확인하고 정규화 된 양식을 반환하십시오 (주요 v
및 메이저, 마이너 및 패치 버전 번호로 전체 릴리스 부품없이).
예 : :
normalized : (( semver("v1.2-beta.1") ))
해결합니다
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
메타 데이터 및 사전 제출 정보를 생략하는 시맨틱 버전의 릴리스 부분을 반환하십시오.
예 : :
release : (( semverrelease("v1.2.3-beta.1") ))
해결합니다
release : v1.2.3
추가 문자열 인수가 주어지면이 함수는 메타 데이터를 보존하는 주어진 시맨틱 버전의 릴리스로 릴리스를 대체합니다.
예 : :
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
해결합니다
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
주어진 시맨틱 버전의 주요 버전 번호를 결정하십시오. 결과는 정수입니다.
예 : :
major : (( semvermajor("1.2.3-beta.1") ))
해결합니다
major : 1
semverincmajor
함수는 주요 버전 번호를 증가시키고 마이너 버전, 패치 버전 및 릴리스 접미사를 재설정하는 데 사용될 수 있습니다.
예 : :
new : (( semverincmajor("1.2.3-beta.1") ))
해결합니다
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
주어진 시맨틱 버전의 사소한 버전 번호를 결정하십시오. 결과는 정수입니다.
예 : :
minor : (( semverminor("1.2.3-beta.1") ))
해결합니다
minor : 2
semverincminor
함수를 사용하여 작은 버전 번호를 증가시키고 패치 버전을 재설정하고 접미사를 릴리스 할 수 있습니다.
예 : :
new : (( semverincmajor("v1.2.3-beta.1") ))
해결합니다
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
주어진 시맨틱 버전의 패치 버전 번호를 결정하십시오. 결과는 정수입니다.
예 : :
patch : (( semverpatch("1.2.3-beta.1") ))
해결합니다
patch : 3
semverincpatch
함수는 패치 버전 번호를 증가 시키거나 릴리스 접미사를 재설정하는 데 사용될 수 있습니다. RLEASE 접미사가 있으면 제거되고 릴리스 정보가 변경되지 않으면 패치 버전 번호가 증가합니다.
예 : :
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
해결합니다
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
주어진 시맨틱 버전의 사전을 결정하십시오. 결과는 문자열입니다.
예 : :
prerelease : (( semverprerelease("1.2.3-beta.1") ))
해결합니다
prerelease : beta.1
추가 문자열 인수 에이 함수 세트가 주어지면 비어있는 문자열로 설정된 경우 대체 또는 지우기
예 : :
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
해결합니다
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
주어진 시맨틱 버전의 메타 데이터를 결정하십시오. 결과는 문자열입니다.
예 : :
metadata : (( semvermetadata("1.2.3+demo") ))
해결합니다
metadata : demo
추가 문자열 인수 에이 함수 세트가 주어지면 메타 데이터를 비어있는 경우 (빈 문자열로 설정된 경우) 대체 또는 지우기.
예 : :
new : (( semvermetadata("1.2.3-test", "demo) ))
해결합니다
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
두 가지 시맨틱 버전을 비교하십시오. 사전은 항상 최종 릴리스보다 작습니다 . 결과는 다음 값을 가진 정수입니다.
결과 | 의미 |
---|---|
-1 | 첫 번째 버전은 두 번째 버전 이전입니다 |
0 | 두 버전 모두 동일합니다 |
1 | 첫 번째 Versuon은 두 번째 후입니다 |
예 : :
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
해결합니다
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
주어진 시맨틱 버전을 금기 목록과 일치시킵니다. 결과는 부울입니다. 버전 제약 조건을 수정할 수 있습니다. 제약이 주어지지 않으면 기능은 주어진 문자열이 시맨틱 버전인지 확인합니다.
예 : :
match : (( semvermatch("1.2.3", "~1.2") ))
해결합니다
match : true
가능한 제약 조건 사양의 전체 목록은 여기에서 찾을 수 있습니다.
(( semversort("1.2.3", "1.2.1") ))
오름차순으로 버전 목록을 정렬하십시오. 주요 v
보존됩니다.
예 : :
sorted : (( semversort("1.2.3", "1.2.1") ))
해결합니다
sorted :
- 1.2.1
- 1.2.3
정렬 할 버전 목록은 단일 목록 인수로 지정 될 수도 있습니다.
Spiff는 X509 인증서 및 키로 작동하는 유용한 기능을 지원합니다. 상태를 제공하기위한 몇 가지 팁을 찾으려면 유용한 섹션을 참조하십시오.
(( x509genkey(spec) ))
이 기능은 개인 RSA 또는 ECDSA 키를 생성 할 수 있습니다. 결과는 다중 라인 문자열 값으로 PEM 인코딩 키가됩니다. 키 크기 (정수 또는 문자열)가 인수로 주어지면 주어진 키 크기 (예 : 2048)로 RSA 키가 생성됩니다. 문자열 값 중 하나가 주어졌습니다
함수는 적절한 ECDSA 키를 생성합니다.
예 : :
keys :
key : (( x509genkey(2048) ))
같은 것으로 해결됩니다
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
PEM 형식의 주어진 키 또는 인증서 (예 : X509Genkey 함수로 생성)에 대해이 함수는 공개 키를 추출하고 PEM 형식으로 다시 회전합니다.
예 : :
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
같은 것으로 해결됩니다
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
public : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rnsCxMx
vSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb325Bf/
...
VzYqyeQyvvRbNe73BXc5temCaQayzsbghkoWK+Wrc33yLsvpeVQBcB93Xhus+Lt1
1lxsoIrQf/HBsiu/5Q3M8L6klxeAUcDbYwIDAQAB
-----END RSA PUBLIC KEY-----
SSH 공개 키를 생성하려면 선택적 추가 형식 인수를 ssh
로 설정할 수 있습니다. 결과는 SSH에 사용할 수있는 정기적 인 공개 키 형식이 될 것입니다. 기본 형식은 pem
이며 위에 표시된 PEM 출력 형식을 제공합니다.
RSA 키는 기본적으로 PEM의 PKCS#1 형식 ( RSA PUBLIC KEY
)으로 마샬링됩니다. 일반 PKIX 형식 ( PUBLIC KEY
)이 필요한 경우 형식 인수 pkix
제공되어야합니다.
ssh
형식을 사용 하여이 기능을 사용하여 PEM 형식의 공개 키를 SSH 키로 변환 할 수도 있습니다.
(( x509cert(spec) ))
함수 x509cert
자체 서명 된 하나 또는 주어진 CA에 의해 서명 된 인증서 인 로컬로 서명 된 인증서를 생성합니다. PEM 인코딩 된 인증서를 멀티 라인 문자열 값으로 반환합니다.
단일 사양 매개 변수는 인증서 정보를 지정하는 데 사용되는 옵션 및 비 선택적 필드가 포함 된 맵을 취합니다. 인라인 맵 표현식 또는 나머지 YAML 문서에 대한지도 참조 일 수 있습니다.
다음지도 필드가 관찰됩니다.
필드 이름 | 유형 | 필수의 | 의미 |
---|---|---|---|
commonName | 끈 | 선택 과목 | 주제의 일반적인 이름 필드 |
organization | 문자열 또는 문자열 목록 | 선택 과목 | 주제의 조직 분야 |
country | 문자열 또는 문자열 목록 | 선택 과목 | 주제의 국가 분야 |
isCA | 부울 | 선택 과목 | CA 옵션 인증서 옵션 |
usage | 문자열 또는 문자열 목록 | 필수의 | 인증서의 사용 키 (아래 참조) |
validity | 정수 | 선택 과목 | 몇 시간 동안의 유효성 간격 |
validFrom | 끈 | 선택 과목 | "1 월 1 일 01:22:31 2019"형식의 시작 시간 " |
hosts | 문자열 또는 문자열 목록 | 선택 과목 | DNS 이름 또는 IP 주소 목록 |
privateKey | 끈 | 필수 또는 공개 키 | 인증서를 생성하기위한 개인 키 |
publicKey | 끈 | 필수 또는 개인 키 | 인증서를 생성하기위한 공개 키 |
caCert | 끈 | 선택 과목 | 서명 할 증명서 |
caPrivateKey | 끈 | 선택 과목 | caCert 의 PRIAVTE 키 |
자체 서명 된 인증서의 경우 privateKey
필드를 설정해야합니다. publicKey
와 ca
필드는 생략해야합니다. caCert
필드가 제공되면 caKey
필드도 필요합니다. privateKey
필드가 caCert
와 함께 제공되면 인증서의 공개 키는 개인 키에서 추출됩니다.
추가 필드는 조용히 무시됩니다.
다음 사용법 키가 지원됩니다 (사례는 무시됩니다).
열쇠 | 의미 |
---|---|
Signature | x509. KeyUsageAdigitalSignature |
Commitment | x509. KeyusAgeContentCommitment |
KeyEncipherment | x509. KeyusAgeKeyEncipherment |
DataEncipherment | x509. KeyUsagedAtaEncipherment |
KeyAgreement | x509. KeyUsageKeyAgreement |
CertSign | x509. KeyusAgecertsign |
CRLSign | x509. KeyusAgeCrlSign |
EncipherOnly | x509. KeyUsageEncipheronly |
DecipherOnly | x509. KeyUsagedEcipheronly |
Any | x509.extkeyUsAgeany |
ServerAuth | x509.extKeyUsagesErverAuth |
ClientAuth | x509.extkeyusageclientauth |
codesigning | x509.extkeyusagecodesigning |
EmailProtection | x509.extKeyUsageEmailProtection |
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 |
예 : :
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
자체 서명 된 루트 인증서를 생성하고
cakey : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
cacert : |+
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIQb5ex4iGfyCcOa1RvnKSkMDANBgkqhkiG9w0BAQsFADAk
MQ8wDQYDVQQKEwZTQVAgU0UxETAPBgNVBAMTCGdhcmRlbmVyMB4XDTE4MTIzMTE0
...
pOUBE3Tgim5rnpa9K9RJ/m8IVqlupcONlxQmP3cCXm/lBEREjODPRNhU11DJwDdJ
5fd+t5SMEit2BvtTNFXLAwz48EKTxsDPdnHgiQKcbIV8NmgUNPHwXaqRMBLqssKl
Cyvds9xGtAtmZRvYNI0=
-----END CERTIFICATE-----
(( x509parsecert(cert) ))
이 기능은 PEM 형식으로 제공된 인증서를 구문 분석하고 필드 맵을 반환합니다.
필드 이름 | 유형 | 필수의 | 의미 |
---|---|---|---|
commonName | 끈 | 선택 과목 | 주제의 일반적인 이름 필드 |
organization | 문자열 목록 | 선택 과목 | 주제의 조직 분야 |
country | 문자열 목록 | 선택 과목 | 주제의 국가 분야 |
isCA | 부울 | 언제나 | CA 옵션 인증서 옵션 |
usage | 문자열 목록 | 언제나 | 인증서의 사용 키 (아래 참조) |
validity | 정수 | 언제나 | 몇 시간 동안의 유효성 간격 |
validFrom | 끈 | 언제나 | start time in the format "Jan 1 01:22:31 2019" |
validUntil | 끈 | 언제나 | start time in the format "Jan 1 01:22:31 2019" |
hosts | string list | 선택 과목 | List of DNS names or IP addresses |
dnsNames | string list | 선택 과목 | List of DNS names |
ipAddresses | string list | 선택 과목 | List of IP addresses |
publicKey | 끈 | 언제나 | public key to generate the certificate for |
eg:
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) ))
resolves to
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.
eg:
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-
eg:
keys :
key : (( wggenkey() ))
public : (( wgpublickey(key)
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
public : n405KfwLpfByhU9pOu0A/ENwp0njcEmmQQJvfYHHQ2M=
(( lambda |x|->x ":" port ))
Lambda expressions can be used to define additional anonymous functions. They can be assigned to yaml nodes as values and referenced with path expressions to call the function with approriate arguments in other dynaml expressions. For the final document they are mapped to string values.
There are two forms of lambda expressions. 하는 동안
lvalue : (( lambda |x|->x ":" port ))
yields a function taking one argument by directly taking the elements from the dynaml expression,
string : " |x|->x " : " port "
lvalue : (( lambda string ))
evaluates the result of an expression to a function. The expression must evaluate to a function or string. If the expression is evaluated to a string it parses the function from the string.
Since the evaluation result of a lambda expression is a regular value, it can also be passed as argument to function calls and merged as value along stub processing.
A complete example could look like this:
lvalue : (( lambda |x,y|->x + y ))
mod : (( lambda|x,y,m|->(lambda m)(x, y) + 3 ))
value : (( .mod(1,2, lvalue) ))
yields
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.
eg:
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.
eg:
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.
eg:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(3=1, 1) ))
As such, this feature seems to be quite useless, but it shows its power if combined with optional parameters or currying as shown in the next paragraphs.
A lambda expression might refer to absolute or relative nodes of the actual yaml document of the call. Relative references are evaluated in the context of the function call. 그러므로
lvalue : (( lambda |x,y|->x + y + offset ))
offset : 0
values :
offset : 3
value : (( .lvalue(1,2) ))
yields 6
for values.value
.
Besides the specified parameters, there is an implicit name ( _
), that can be used to refer to the function itself. It can be used to define self recursive function. Together with the logical and conditional operators a fibunacci function can be defined:
fibonacci : (( lambda |x|-> x <= 0 ? 0 :x == 1 ? 1 :_(x - 2) + _( x - 1 ) ))
value : (( .fibonacci(5) ))
yields the value 8
for the value
property.
By default reference expressions in a lambda expression are evaluated in the static scope of the lambda dedinition followed by the static yaml scope of the caller. Absolute references are always evalated in the document scope of the caller.
The name _
can also be used as an anchor to refer to the static definition scope of the lambda expression in the yaml document that was used to define the lambda function. Those references are always interpreted as relative references related to the this static yaml document scope. There is no denotation for accessing the root element of this definition scope.
Relative names can be used to access the static definition scope given inside the dynaml expression (outer scope literals and parameters of outer lambda parameters)
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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
.
eg:
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.
eg:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry : (( .func(c=3, 2) ))
result : (( .curry(5) ))
evalutes result
to
result :
a : 2
b : 5
c : 3
The resulting function keeps the parameter b
. Hereby the default value will be kept. Therefore it can just be called without argument ( .curry()
), which would produce
result :
a : 2
b : 1
c : 3
주목 :
For compatibility reasons currying is also done, if a lambda function without defaulted parameters is called with less arguments than declared parameters.
This behaviour is deprecated and will be removed in the future. It is replaced by the currying operator.
eg:
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.
eg:
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.
eg:
data :
alice : 25
result : (( sync[data|v|->defined(v.alice),v.alice] ))
resolves to
data :
alice : 25
result : 25
This example is quite useless, because the sync expression is a constant. It just demonstrates the usage.
Mappings are used to produce a new list from the entries of a list or map , or a new map from entries of a map containing the entries processed by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the mapping function: It can be inlined as in (( map[list|x|->x ":" port] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( map[list|mapping.expression))
(here the mapping is taken from the property mapping.expression
, which should hold an approriate lambda function).
The mapping comes in two target flavors: with []
or {}
in the syntax. The first flavor always produces a list from the entries of the given source. The second one takes only a map source and produces a filtered or transformed map .
Additionally the mapping uses three basic mapping behaviours:
map
. Here the result of the lambda function is used as new value to replace the original one. 또는select
. Here the result of the lambda function is used as a boolean to decide whether the entry should be kept ( true
) or omitted ( false
).sum
. Here always the list flavor is used, but the result type and content is completely determined by the parameterization of the statement by successively aggregating one entry after the other into an arbitrary initial value. Note : The special reference _
is not set for inlined lambda functions as part of the mapping syntax. Therefore the mapping statements (and all other statements using inlined lambda functions as part of their syntax) can be used inside regular lambda functions without hampering the meaning of this special refrence for the surrounding explicit lambda expression.
(( map[list|elem|->dynaml-expr] ))
Execute a mapping expression on members of a list to produce a new (mapped) list. The first expression ( list
) must resolve to a list. The last expression ( x ":" port
) defines the mapping expression used to map all members of the given list. Inside this expression an arbitrarily declared simple reference name (here x
) can be used to access the actually processed list element.
예를 들어
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
yields
port : 4711
hosts :
- alice
- bob
mapped :
- alice:4711
- bob:4711
This expression can be combined with others, for example:
port : 4711
list :
- alice
- bob
joined : (( join( ", ", map[list|x|->x ":" port] ) ))
which magically provides a comma separated list of ported hosts:
port : 4711
list :
- alice
- bob
joined : alice:4711, bob:4711
(( map[list|idx,elem|->dynaml-expr] ))
In this variant, the first argument idx
is provided with the index and the second elem
with the value for the index.
예를 들어
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
yields
list :
- name : alice
age : 25
- name : bob
age : 24
ages :
- 1. alice is 25
- 2. bob is 24
(( map[map|key,value|->dynaml-expr] ))
Mapping of a map to a list using a mapping expression. The expression may have access to the key and/or the value. If two references are declared, both values are passed to the expression, the first one is provided with the key and the second one with the value for the key. If one reference is declared, only the value is provided.
예를 들어
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
yields
ages :
alice : 25
bob : 24
keys :
- alice
- bob
(( map{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys and the new entry values. As for a list mapping additionally a key variable can be specified in the variable list.
persons :
alice : 27
bob : 26
older : (( map{persons|x|->x + 1} ) ))
just increments the value of all entries by one in the field older
:
older :
alice : 28
bob : 27
주목
An alternate way to express the same is to use sum[persons|{}|s,k,v|->s { k = v + 1 }]
.
(( map{list|elem|->dynaml-expr} ))
Using {}
instead of []
together with a list in the mapping syntax, the result is again a map with the list elements as key and the mapped entry values. For this all list entries must be strings. As for a list mapping additionally an index variable can be specified in the variable list.
persons :
- alice
- bob
length : (( map{persons|x|->length(x)} ) ))
just creates a map mapping the list entries to their length:
length :
alice : 5
bob : 3
(( select[expr|elem|->dynaml-expr] ))
With select
a map or list can be filtered by evaluating a boolean expression for every entry. An entry is selected if the expression evaluates to true equivalent value. (see conditions).
Basically it offers all the mapping flavors available for map[]
예를 들어
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
주목
An alternate way to express the same is to use map[list|v|->v.age > 25 ? v :~]
.
(( select{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys filtered by the given expression.
persons :
alice : 25
bob : 26
older : (( select{persons|x|->x > 25} ))
just keeps all entries with a value greater than 25 and omits all others:
selected :
bob : 26
This flavor only works on maps .
주목
An alternate way to express the same is to use sum[persons|{}|s,k,v|->v > 25 ? s {k = v} :s]
.
Aggregations are used to produce a single result from the entries of a list or map aggregating the entries by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the aggregation function: It can be inlined as in (( sum[list|0|s,x|->s + x] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( sum[list|0|aggregation.expression))
(here the aggregation function is taken from the property aggregation.expression
, which should hold an approriate lambda function).
(( sum[list|initial|sum,elem|->dynaml-expr] ))
Execute an aggregation expression on members of a list to produce an aggregation result. The first expression ( list
) must resolve to a list. The second expression is used as initial value for the aggregation. The last expression ( s + x
) defines the aggregation expression used to aggregate all members of the given list. Inside this expression an arbitrarily declared simple reference name (here s
) can be used to access the intermediate aggregation result and a second reference name (here x
) can be used to access the actually processed list element.
예를 들어
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
yields
list :
- 1
- 2
sum : 3
(( sum[list|initial|sum,idx,elem|->dynaml-expr] ))
In this variant, the second argument idx
is provided with the index and the third elem
with the value for the index.
예를 들어
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
yields
list :
- 1
- 2
- 3
prod : 8
(( sum[map|initial|sum,key,value|->dynaml-expr] ))
Aggregation of the elements of a map to a single result using an aggregation expression. The expression may have access to the key and/or the value. The first argument is always the intermediate aggregation result. If three references are declared, both values are passed to the expression, the second one is provided with the key and the third one with the value for the key. If two references are declared, only the second one is provided with the value of the map entry.
예를 들어
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
yields
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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
eg:
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
.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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).)
eg:
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.
eg:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
eg:
template.yaml
data :
alice : (( &tag:alice(25) ))
tagref : (( alice::. ))
resolves tagref
to 25
(( foo.bar::alice ))
Tag names may be structured. A tag name consists of a non-empty list of tag components separated by a dot or colon ( :
). A tag component may contain ASCII letters or numbers, starting wit a letter. Multi-component tags are subject to Tag Resolution.
A tag reference always contains a tag name and a path separated by a double colon ( ::
). The standard use-case is to describe a dedicated sub node for a tagged node value.
for example, if the tag X
describes the value
data :
alice : 25
bob : 24
the tagged reference X::data.alice
describes the value 25
.
For tagged references with a path other than .
(the whole tag value), structured tags feature a more sophisticated resolution mechanism. A structured tag consist of multiple tag components separated by a colon ( :
), for example lib:mylib
. Therefore, tags span a tree of namespaces or scopes used to resolve path references. A tag-less reference just uses the actual document or binding to resolve a path expression.
Evaluation of a path reference for a tag tries to resolve the path in the first tag tree level where the path is available (breadth-first search). If this level contains multiple tags that could resolve the given path, the resolution fails because it cannot be unambigiously resolved.
예를 들어:
tags :
- << : (( &tag:lib:alice ))
data : alice.alice
- << : (( &tag:lib:alice:v1))
data : alice.v1
- << : (( &tag:lib:bob))
other : bob
usage :
data : (( lib::data ))
effectively resolves usage.data
to lib:alice::data
and therefore to the value alice.alice
.
To achieve this all matching sub tags are orderd by their number of tag components. The first sub-level tag containing such a given path is selected. For this level, the matching tag must be non-ambigious. There must only be one tag with this level containing a matching path. If there are multiple ones the evaluation fails. In the above example this would be the case if tag lib:bob
would contain a field data
instead of or additional to other
.
This feature can be used in library stubs to provide qualified names for their elements that can be used with merging the containing document nodes into the template.
If the template file is a multi-document stream the tags are preserved during the complete processing. This means tags defined in a earlier document can be used in all following documents, also. But the tag names must be unique across all documents in a multi-document stream.
eg:
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
eg:
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:
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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 | 유형 | 의미 |
---|---|---|
VERSION | 끈 | current version of spiff |
FILE | 끈 | name of actually processed template file |
DIR | 끈 | name of directory of actually processed template file |
RESOLVED_FILE | 끈 | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | 끈 | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | 끈 | path name of actually processed field |
PATH | list[string] | path name as component list |
OUTER | yaml doc | outer documents for nested merges, index 0 is the next outer document |
BINDINGS | yaml doc | the external bindings for the actual processing (see also ___) |
If external bindings are specified they are the last elements in OUTER
.
eg:
template.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
eg:
foo :
bar :
dir : .
file : template.yml
path :
- foo
- bar
- path
str : foo.bar.str
Dynaml expressions are evaluated obeying certain priority levels. This means operations with a higher priority are evaluated first. For example the expression 1 + 2 * 3
is evaluated in the order 1 + ( 2 * 3 )
. Operations with the same priority are evaluated from left to right (in contrast to version 1.0.7). This means the expression 6 - 3 - 2
is evaluated as ( 6 - 3 ) - 2
.
The following levels are supported (from low priority to high priority)
||
, //
foo bar
)-or
, -and
==
, !=
, <=
, <
, >
, >=
+
, -
*
, /
, %
( )
, !
, constants, references ( foo.bar
), merge
, auto
, lambda
, map[]
, and functionsThe complete grammar can be found in dynaml.peg.
Feature state: alpha
Attention: This is an alpha feature. It must be enabled on the command line with the --interpolation
or --features=interpolation
option. Also for the spiff library it must explicitly be enabled. By adding the key interpolation
to the feature list stored in the environment variable SPIFF_FEATURES
this feature will be enabled by default.
Typically a complete value can either be a literal or a dynaml expression. For string literals it is possible to use an interpolation syntax to embed dynaml expressions into strings.
예를 들어
data : test
interpolation : this is a (( data ))
replaces the part between the double brackets by the result of the described expression evaluation. Here the brackets can be escaped by the usual escaping ( ((!
) syntax.
Those string literals will implicitly be converted to complete flat dynaml expressions. The example above will therefore be converted into
(( "this is a " data ))
which is the regular dynaml equivalent. The escaping is very ticky, and may be there are still problems. Quotes inside an embedded dynaml expression can be escaped to enable quotes in string literals.
Incomplete or partial interpolation expressions will be ignored and just used as string.
Strings inside a dynaml expression are NOT directly interpolated again, thus
data : " test "
interpolated : " this is a (( length( " (( data )) " ) data )) "
will resolve interpolation
to this is 10test
and not to this is 4test
.
But if the final string after the expression evaluation again describes a string interpolation it will be processed, again.
data : test
interpolation : this is a (( "(( data ))" data ))
will resolve interpolation
to this is testtest
.
The embedded dynaml expression must be concatenatable with strings.
Feature state: alpha
In addition to describe conditions and loops with dynaml expressions it is also possible to use elements of the document structure to embed control structures.
Such a YAML-based control structure is always described as a map in YAML/JSON. The syntactical elements are expressed as map fields starting with <<
. Additionally, depending on the control structure, regular fields are possible. Control structures finally represent a value for the containing node. They may contain marker expressions ( <<
), also.
eg:
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.
eg:
list :
- <<if : (( features("control") ))
<<then : alice
- <<if : (( features("control") ))
<<then :
- - peter
- <<if : (( features("control") ))
<<then :
- bob
resolves to
list :
- alice
- - peter
- bob
<<if:
The condition structure is defined by the syntax field <<if
. It additionally accepts the fields <<then
and <<else
.
The condition field must provide a boolean value. If it is true
the optional <<then
field is used to substitute the control structure, otherwise the optional <<else
field is used.
If the appropriate case is not specified, the result is the undefined (( ~~ ))
value. The containing field is therefore completely omitted from the output.
.eg:
x : test1
cond :
field :
<<if : (( x == "test" ))
<<then : alice
<<else : bob
evaluates cond.field
to bob
If the else case is omitted, the cond
field would be an empty map ( field
is omitted, because the contained control structure evaluates to undefined )
A comparable way to do this with regular dynaml could look like this:
cond : (( x == "test" ? "alice" :"bob" ))
A better way more suitable for complex cases would be:
local :
<< : (( &local))
then : alice
else : bob
cond : (( x == "test" ? local.then :local.else ))
<<switch:
The switch
control structure evaluates the switch value of the <<switch
field to a string and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
The nil value matches the default
case. If the switch value is undefined the control evaluates to the undefined value (( ~~ ))
.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
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.
eg:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
또는
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( bob == 2 ? ~~ :key ))
<<do : (( bob ))
resolve to
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
b1 : 1
b3 : 3
<<merge:
With merge
it is possible to merge maps given as list value of the <<merge
field with regular map fields from the control structure to determine the final map value.
The value for <<merge:
may be a single map or a list of maps to join with the directly given fields.
eg:
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:
eg:
x : charlie
map :
<<merge :
- <<if : (( x == "charlie" ))
<<then :
charlie : 27
- <<if : (( x == "alice" ))
<<then :
alice : 20
alice : 25
charlie : 2
resolves to
x : charlie
map :
alice : 25
charlie : 27
By default spiff
performs a deep structural merge of its first argument, the template file, with the given stub files. The merge is processed from right to left, providing an intermediate merged stub for every step. This means, that for every step all expressions must be locally resolvable.
Structural merge means, that besides explicit dynaml merge
expressions, values will be overridden by values of equivalent nodes found in right-most stub files. In general, flat value lists are not merged. Only lists of maps can be merged by entries in a stub with a matching index.
There is a special support for the auto-merge of lists containing maps, if the maps contain a name
field. Hereby the list is handled like a map with entries according to the value of the list entries' name
field. If another key field than name
should be used, the key field of one list entry can be tagged with the prefix key:
to indicate the indended key name. Such tags will be removed for the processed output.
In general the resolution of matching nodes in stubs is done using the same rules that apply for the reference expressions (( foo.bar.[1].baz )).
For example, given the file template.yml :
foo :
- name : alice
bar : template
- name : bob
bar : template
plip :
- id : 1
plop : template
- id : 2
plop : template
bar :
- foo : template
list :
- a
- b
and file stub.yml :
foo :
- name : bob
bar : stub
plip :
- key:id : 1
plop : stub
bar :
- foo : stub
list :
- c
- d
spiff merge template.yml stub.yml
보고
foo :
- bar : template
name : alice
- bar : stub
name : bob
plip :
- id : 1
plop : stub
- id : 2
plop : template
bar :
- foo : stub
list :
- a
- b
Be careful that any name:
key in the template for the first element of the plip
list will defeat the key:id: 1
selector from the stub. When a name
field exist in a list element, then this element can only be targeted by this name. When the selector is defeated, the resulting value is the one provided by the template.
Merging the following files in the given order
deployment.yml
networks : (( merge ))
cf.yml
utils : (( merge ))
network : (( merge ))
meta : (( merge ))
networks :
- name : cf1
<< : (( utils.defNet(network.base.z1,meta.deployment_no,30) ))
- name : cf2
<< : (( utils.defNet(network.base.z2,meta.deployment_no,30) ))
infrastructure.yml
network :
size : 16
block_size : 256
base :
z1 : 10.0.0.0
z2 : 10.1.0.0
rules.yml
utils :
defNet : (( |b,n,s|->(*.utils.network).net ))
network :
<< : (( &template ))
start : (( b + n * .network.block_size ))
first : (( start + ( n == 0 ? 2 :0 ) ))
lower : (( n == 0 ? [] :b " - " start - 1 ))
upper : (( start + .network.block_size " - " max_ip(net.subnets.[0].range) ))
net :
subnets :
- range : (( b "/" .network.size ))
reserved : (( [] lower upper ))
static :
- (( first " - " first + s - 1 ))
instance.yml
meta :
deployment_no : 1
will yield a network setting for a dedicated deployment
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0 - 10.0.255.255
static :
- 10.0.1.0 - 10.0.1.29
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.0.0 - 10.1.0.255
- 10.1.2.0 - 10.1.255.255
static :
- 10.1.1.0 - 10.1.1.29
Using the same config for another deployment of the same type just requires the replacement of the instance.yml
. Using a different instance.yml
meta :
deployment_no : 0
will yield a network setting for a second deployment providing the appropriate settings for a unique other IP block.
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.1.0 - 10.0.255.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.1.0 - 10.1.255.255
static :
- 10.1.0.2 - 10.1.0.31
If you move to another infrastructure you might want to change the basic IP layout. You can do it just by adapting the infrastructure.yml
network :
size : 17
block_size : 128
base :
z1 : 10.0.0.0
z2 : 10.0.128.0
Without any change to your other settings you'll get
networks :
- name : cf1
subnets :
- range : 10.0.0.0/17
reserved :
- 10.0.0.128 - 10.0.127.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.0.128.0/17
reserved :
- 10.0.128.128 - 10.0.255.255
static :
- 10.0.128.2 - 10.0.128.31
There are several scenarios yielding results that do not seem to be obvious. Here are some typical pitfalls.
The auto merge never adds nodes to existing structures
For example, merging
template.yml
foo :
alice : 25
~와 함께
stub.yml
foo :
alice : 24
bob : 26
yields
foo :
alice : 24
Use <<: (( merge )) to change this behaviour, or explicitly add desired nodes to be merged:
template.yml
foo :
alice : 25
bob : (( merge ))
Simple node values are replaced by values or complete structures coming from stubs, structures are deep merged.
For example, merging
template.yml
foo : (( ["alice"] ))
~와 함께
stub.yml
foo :
- peter
- paul
yields
foo :
- peter
- paul
But the template
foo : [ (( "alice" )) ]
is merged without any change.
Expressions are subject to be overridden as a whole
A consequence of the behaviour described above is that nodes described by an expession are basically overridden by a complete merged structure, instead of doing a deep merge with the structues resulting from the expression evaluation.
For example, merging
template.yml
men :
- bob : 24
women :
- alice : 25
people : (( women men ))
~와 함께
stub.yml
people :
- alice : 13
yields
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
yields
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
eg:
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.
eg:
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.
eg:
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.
eg:
sum : (( sum[[1..4]|init|s,e|->*temp] ))
temp :
<< : (( &template ))
sum : (( s.sum + e ))
prd : (( s.prd * e ))
init :
sum : 0
prd : 1
yields for sum
the value
sum:
prd: 24
sum: 10
Taking advantage of the undefined value
At first glance it might look strange to introduce a value for undefined . But it can be really useful as will become apparent with the following examples.
Whenever a stub syntactically defines a field it overwrites the default in the template during merging. Therefore it would not be possible to define some expression for that field that eventually keeps the default value. Here the undefined value can help:
eg: merging
template.yml
alice : 24
bob : 25
~와 함께
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
yields
alice : ~
bob : 25
There is a problem accessing upstream values. This is only possible if the local stub contains the definition of the field to use. But then there will always be a value for this field, even if the upstream does not overwrite it.
Here the undefined value can help by providing optional access to upstream values. Optional means, that the field is only defined, if there is an upstream value. Otherwise it is undefined for the expressions in the local stub and potential downstream templates. This is possible because the field is formally defined, and will therefore be merged, only after evaluating the expression if it is not merged it will be removed again.
eg: merging
template.yml
alice : 24
bob : 25
peter : 26
~와 함께
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
그리고
config.yml
config :
alice : 4711
peter : 0815
yields
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.
eg:
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.
eg:
range : (( (|cidr,first,size|->(*templates.addr).range)("10.0.0.0/16",10,255) ))
templates :
addr :
<< : (( &template ))
base : (( min_ip(cidr) ))
start : (( base + first ))
end : (( start + size - 1 ))
range : (( start " - " end ))
evaluates range
to
range : 10.0.0.10 - 10.0.1.8
...
Defaulting and Requiring Fields
Traditionally defaulting in spiff is done by a downstream template where the playload data file is used as stub.
Fields with simple values can just be specified with their values. They will be overwritten by stubs using the regular spiff document merging mechanisms.
It is more difficult for maps or lists. If a map is specified in the template only its fields will be merged (see above), but it is never replaced as a whole by settings in the playload definition files. And Lists are never merged.
Therefore maps and lists that should be defaulted as a whole must be specified as initial expressions (referential or inline) in the template file.
eg: merging of
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
그리고
payload.yaml
config :
value2 : configured
othervalue : I want this but don't get it
evaluates to
config :
person :
age : bob
name : alice
value1 : defaultvalue
value2 : configured
In such a scenario the structure of the resulting document is defined by the template. All kinds of variable fields or sub-structures must be forseen by the template by using <<: (( merge ))
expressions in maps.
eg: changing template to
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
<< : (( merge ))
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
Known optional fields can be described using the undefined ( ~~
) expression:
template.yaml
config :
optional : (( ~~ ))
Such fields will only be part of the final document if they are defined in an upstream stub, otherwise they will be completely removed.
Required fields can be defined with the expression (( merge ))
. If no stub contains a value for this field, the merge cannot be fullfilled and an error is reported. If a dedicated message should be shown instead, the merge expression can be defaulted with an error function call.
eg:
template.yaml
config :
password : (( merge || error("the field password is required") ))
will produce the following error if no stub contains a value:
error generating manifest: unresolved nodes:
(( merge || error("the field password is required") )) in c.yaml config.password () *the field password is required
This can be simplified by reducing the expression to the sole error
expression.
Besides this template based defaulting it is also possible to provide defaults by upstream stubs using the &default
marker. Here the payload can be a downstream file.
X509 and providing State
When generating keys or certificates with the X509 Functions there will be new keys or certificates for every execution of spiff . But it is also possible to use spiff to maintain key state. A very simple script could look like this:
#! /bin/bash
DIR= " $( dirname " $0 " ) /state "
if [ ! -f " $DIR /state.yaml " ] ; then
echo " state: " > " $DIR /state.yaml "
fi
spiff merge " $DIR /template.yaml " " $DIR /state.yaml " > " $DIR /. $$ " && mv " $DIR /. $$ " " $DIR /state.yaml "
It uses a template file (containing the rules) and a state file with the actual state as stub. The first time it is executed there is an empty state and the rules are not overridden, therefore the keys and certificates are generated. Later on, only additional new fields are calculated, the state fields already containing values just overrule the dynaml expressions for those fields in the template.
If a re-generation is required, the state file can just be deleted.
A template may look like this:
state/template.yaml
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : rootca
privateKey : (( state.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
peer :
organization : Mandelsoft
commonName : etcd
publicKey : (( state.pub ))
caCert : (( state.cacert ))
caPrivateKey : (( state.cakey ))
validity : 100
usage :
- ServerAuth
- ClientAuth
- KeyEncipherment
hosts :
- etcd.mandelsoft.org
state :
cakey : (( x509genkey(2048) ))
capub : (( x509publickey(cakey) ))
cacert : (( x509cert(spec.ca) ))
key : (( x509genkey(2048) ))
pub : (( x509publickey(key) ))
peer : (( x509cert(spec.peer) ))
The merge then generates a rootca and some TLS certificate signed with this CA.
Generating, Deploying and Accessing Status for Kubernetes Resources
The sync
function offers the possibility to synchronize the template processing with external content. This can also be the output of a command execution. Therefore the template processing can not only be used to generate a deployment manifest, but also for applying this to a target system and retrieving deployment status values for the further processing.
A typical scenario of this kind could be a kubernetes setup including a service of type LoadBalancer . Once deployed it gets assigned status information about the IP address or hostname of the assigned load balancer. This information might be required for some other deployment manifest.
A simple template for such a deployment could like this:
service :
apiVersion : v1
kind : Service
metadata :
annotations :
dns.mandelsoft.org/dnsnames : echo.test.garden.mandelsoft.org
dns.mandelsoft.org/ttl : " 500 "
name : test-service
namespace : default
spec :
ports :
- name : http
port : 80
protocol : TCP
targetPort : 8080
sessionAffinity : None
type : LoadBalancer
deployment :
testservice : (( sync[pipe_uncached(service, "kubectl", "apply", "-f", "-", "-o", "yaml")|value|->defined(value.status.loadBalancer.ingress)] ))
otherconfig :
lb : (( deployment.testservice.status.loadBalancer.ingress ))
Crazy Shit: Graph Analaysis with spiff
It is easy to describe a simple graph with knots and edges (for example for a set of components and their dependencies) just by using a map of lists.
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
Now it would be useful to figure out whether there are dependency cycles or to determine ordered transitive dependencies for a component.
Let's say something like this:
graph :
utilities :
closures : (( utilities.graph.evaluate(graph) ))
cycles : (( utilities.graph.cycles(closures) ))
Indeed, this can be done with spiff. The only thing required is a "small utilities stub" .
utilities :
<< : (( &temporary ))
graph :
_dep : (( |model,comp,closure|->contains(closure,comp) ? { $deps=[], $err=closure [comp]} :($deps=_._deps(model,comp,closure [comp]))($err=sum[deps|[]|s,e|-> length(s) >= length(e.err) ? s :e.err]) { $deps=_.join(map[deps|e|->e.deps]), $err=err} ))
_deps : (( |model,comp,closure|->map[model.[comp]|dep|->($deps=_._dep(model,dep,closure)) { $deps=[dep] deps.deps, $err=deps.err }] ))
join : (( |lists|->sum[lists|[]|s,e|-> s e] ))
min : (( |list|->sum[list|~|s,e|-> s ? e < s ? e :s :e] ))
normcycle : (( |cycle|->($min=_.min(cycle)) min ? sum[cycle|cycle|s,e|->s.[0] == min ? s :(s.[1..] [s.[1]])] :cycle ))
cycle : (( |list|->list ? ($elem=list.[length(list) - 1]) _.normcycle(sum[list|[]|s,e|->s ? s [e] :e == elem ? [e] :s]) :list ))
norm : (( |deps|->{ $deps=_.reverse(uniq(_.reverse(deps.deps))), $err=_.cycle(deps.err) } ))
reverse : (( |list|->sum[list|[]|s,e|->[e] s] ))
evaluate : (( |model|->sum[model|{}|s,k,v|->s { k=_.norm(_._dep(model,k,[]))}] ))
cycles : (( |result|->uniq(sum[result|[]|s,k,v|-> v.err ? s [v.err] :s]) ))
And magically spiff does the work just by calling
spiff merge closure.yaml graph.yaml utilities.yaml
closures :
a :
deps :
- c
- b
- a
err :
- a
- c
- a
b :
deps : []
err : []
c :
deps :
- a
- b
- c
err :
- a
- c
- a
d :
deps :
- b
err : []
e :
deps :
- d
- b
err : []
cycles :
- - a
- c
- a
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
The evaluation of dynaml expressions may fail because of several reasons:
If a dynaml expression cannot be resolved to a value, it is reported by the spiff merge
operation using the following layout:
(( <failed expression> )) in <file> <path to node> (<referred path>) <tag><issue>
(( min_ip("10") )) in source.yml node.a.[0] () *CIDR argument required
Cyclic dependencies are detected by iterative evaluation until the document is unchanged after a step. Nodes involved in a cycle are therefore typically reported just as unresolved node without a specific issue.
The order of the reported unresolved nodes depends on a classification of the problem, denoted by a dedicated tag. The following tags are used (in reporting order):
꼬리표 | 의미 |
---|---|
* | error in local dynaml expression |
@ | dependent or involved in cyclic dependencies |
- | subsequent error because of refering to a yaml node with an error |
Problems occuring during inline template processing are reported as nested problems. The classification is propagated to the outer node.
If a problem occurs in nested lamba calls the call stack together with the lamba function and is local binding is listed.
(( 2 + .func(2) )) in local/err.yaml value () *evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 2}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 1}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 0}
... resolution of template 'template' failed
(( z )) in local/err.yaml val ()*'z' not found
In case of parsing errors in dynaml expressions, the error location is shown now. If it is a multi line expression the line a character/symbol number in that line is show, otherwise the line numer is omitted.
((
2 ++ .func(2)
)) in local/err.yaml faulty () *parse error near line 2 symbol 2 - line 2 symbol 3: " "
Spiff provides a Go package ( spiffing
) that can be used to include spiff templates in Go programs.
An example program could look like this:
import (
"fmt"
"math"
"os"
"github.com/mandelsoft/spiff/dynaml"
"github.com/mandelsoft/spiff/spiffing"
)
func func_pow ( arguments [] interface {}, binding dynaml. Binding ) ( interface {}, dynaml. EvaluationInfo , bool ) {
info := dynaml . DefaultInfo ()
if len ( arguments ) != 2 {
return info . Error ( "pow takes 2 arguments" )
}
a , b , err := dynaml . NumberOperands ( arguments [ 0 ], arguments [ 1 ])
if err != nil {
return info . Error ( "%s" , err )
}
_ , i := a .( int64 )
if i {
r := math . Pow ( float64 ( a .( int64 )), float64 ( b .( int64 )))
if float64 ( int64 ( r )) == r {
return int64 ( r ), info , true
}
return r , info , true
} else {
return math . Pow ( a .( float64 ), b .( float64 )), info , true
}
}
var state = `
state: {}
`
var stub = `
unused: (( input ))
ages:
alice: (( pow(2,5) ))
bob: (( alice + 1 ))
`
var template = `
state:
<<<: (( &state ))
random: (( rand("[:alnum:]", 10) ))
ages: (( &temporary ))
example:
name: (( input )) # direct reference to additional values
sum: (( sum[ages|0|s,k,v|->s + v] ))
int: (( pow(2,4) ))
float: 2.1
pow: (( pow(1.1e1,2.1) ))
`
func Error ( err error ) {
if err != nil {
fmt . Fprintf ( os . Stderr , "Error: %s n " , err )
os . Exit ( 1 )
}
}
func main () {
values := map [ string ] interface {}{}
values [ "input" ] = "this is an input"
functions := spiffing . NewFunctions ()
functions . RegisterFunction ( "pow" , func_pow )
spiff , err := spiffing . New (). WithFunctions ( functions ). WithValues ( values )
Error ( err )
pstate , err := spiff . Unmarshal ( "state" , [] byte ( state ))
Error ( err )
pstub , err := spiff . Unmarshal ( "stub" , [] byte ( stub ))
Error ( err )
ptempl , err := spiff . Unmarshal ( "template" , [] byte ( template ))
Error ( err )
result , err := spiff . Cascade ( ptempl , []spiffing. Node { pstub }, pstate )
Error ( err )
b , err := spiff . Marshal ( result )
Error ( err )
newstate , err := spiff . Marshal ( spiff . DetermineState ( result ))
Error ( err )
fmt . Printf ( "==== new state === n " )
fmt . Printf ( "%s n " , string ( newstate ))
fmt . Printf ( "==== result === n " )
fmt . Printf ( "%s n " , string ( b ))
}
지원합니다