테스트
테스트 (Tests)
Jinja에서 테스트(test)는 템플릿 표현식을 평가해서 True나 False를 돌려주는 방법이에요. Jinja는 이미 많은 내장 테스트를 갖고 있고, 공식 Jinja 템플릿 문서의 기본 제공 테스트를 참고할 수 있어요.
테스트와 필터의 가장 큰 차이는 테스트는 비교에 쓰이고, 필터는 데이터 조작에 쓰인다는 점이에요. 또한 테스트는 map(), select() 같은 목록 처리 필터 안에서도 쓸 수 있어 목록의 항목을 골라내는 데 활용돼요. 다른 모든 템플릿 처리와 마찬가지로 테스트도 항상 Ansible 컨트롤 노드에서 실행되고, 작업의 대상 머신에서는 실행되지 않아요 — 로컬 데이터를 검사하기 때문이에요. Jinja2 테스트 외에도 Ansible은 몇 가지 테스트를 더 제공하고, 사용자가 직접 만들기도 쉬워요.
출처: 문서
본문
테스트 문법
테스트 문법은 필터 문법(variable | filter)과 달라요. 역사적으로 Ansible은 테스트를 Jinja 테스트와 Jinja 필터 양쪽으로 등록해서, 필터 문법으로도 참조할 수 있게 했어요. 하지만 Ansible 2.5부터는 필터로 Jinja 테스트를 쓰면 deprecation 경고가 나고, 2.9+ 부터는 테스트 문법을 쓰는 것이 필수가 됐어요.
Jinja 테스트를 쓰는 문법은 다음과 같아요.
variable is test_name
예를 들면,
result is failed
문자열 테스트
문자열을 부분 문자열(substring)이나 정규식에 매칭할 때는 match, search, regex 테스트를 써요.
vars:
url: "https://example.com/users/foo/resources/bar"
tasks:
- debug:
msg: "matched pattern 1"
when: url is match("https://example.com/users/.*/resources")
- debug:
msg: "matched pattern 2"
when: url is search("users/.*/resources/.*")
- debug:
msg: "matched pattern 3"
when: url is search("users")
- debug:
msg: "matched pattern 4"
when: url is regex("example\.com/\w+/foo")
match는 문자열 시작 부분에 패턴이 있으면 성공하고, search는 문자열 어디에든 패턴이 있으면 성공해요. 기본적으로 regex는 search처럼 동작하지만, match_type 키워드 인자를 넘겨 다른 검사도 수행하도록 구성할 수 있어요. match_type은 검색에 쓰일 re 메서드를 결정해요. 전체 목록은 관련 파이썬 문서에서 확인할 수 있어요.
모든 문자열 테스트는 선택적인 ignorecase와 multiline 인자도 받아요. 각각 파이썬 re 라이브러리의 re.I와 re.M에 대응해요.
Vault
2.10 버전에 새로 추가됐어요.
변수가 인라인 단일 vault 암호화 값인지 확인하려면 vault_encrypted 테스트를 쓸 수 있어요.
vars:
variable: !vault |
$ANSIBLE_VAULT;1.2;AES256;dev
61323931353866666336306139373937316366366138656131323863373866376666353364373761
3539633234313836346435323766306164626134376564330a373530313635343535343133316133
36643666306434616266376434363239346433643238336464643566386135356334303736353136
6565633133366366360a326566323363363936613664616364623437336130623133343530333739
3039
tasks:
- debug:
msg: '{{ (variable is vault_encrypted) | ternary("Vault encrypted", "Not vault encrypted") }}'
참·거짓 (truthiness) 테스트
2.10 버전에 새로 추가됐어요.
Ansible 2.10부터 파이썬처럼 truthy/falsy 검사를 할 수 있어요.
- debug:
msg: "Truthy"
when: value is truthy
vars:
value: "some string"
- debug:
msg: "Falsy"
when: value is falsy
vars:
value: ""
또한 truthy와 falsy 테스트는 convert_bool이라는 선택 매개변수를 받아서 불리언 표시를 실제 불리언으로 변환하려 시도해요.
- debug:
msg: "Truthy"
when: value is truthy(convert_bool=True)
vars:
value: "yes"
- debug:
msg: "Falsy"
when: value is falsy(convert_bool=True)
vars:
value: "off"
버전 비교
1.6 버전에 새로 추가됐어요.
참고: 2.5에서
version_compare는version으로 이름이 바뀌었어요.
버전 번호를 비교할 때, 예를 들어 ansible_facts['distribution_version']이 '12.04'보다 크거나 같은지 확인하려면 version 테스트를 써요.
"{{ ansible_facts['distribution_version'] is version('12.04', '>=') }}"
ansible_facts['distribution_version']이 12.04보다 크거나 같으면 이 테스트는 True를, 그렇지 않으면 False를 돌려줘요.
version 테스트는 다음 연산자를 받아요.
<, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne
이 테스트는 세 번째 매개변수 strict도 받아요. 이는 ansible.module_utils.compat.version.StrictVersion에 정의된 엄격한 버전 파싱을 쓸지 결정해요. 기본값은 False(LooseVersion 사용)이고, True면 엄격한 버전 파싱을 켜요.
{{ sample_version_var is version('1.0', operator='lt', strict=True) }}
Ansible 2.11부터 version 테스트는 strict와 상호 배타적인 version_type 매개변수를 받아요. 값은 다음과 같아요.
loose, strict, semver, semantic, pep440
- loose — 파이썬
distutils.version.LooseVersion클래스에 해당해요. 모든 버전 형식이 이 타입에 유효해요. 비교 규칙은 단순하고 예측 가능하지만, 항상 기대한 결과를 주지는 않을 수 있어요. - strict — 파이썬
distutils.version.StrictVersion클래스에 해당해요. 버전 번호는 점으로 구분된 2~3개의 숫자 구성 요소로 이뤄지고, 끝에 선택적인 '프리릴리즈' 태그가 붙을 수 있어요. 프리릴리즈 태그는 'a'나 'b'라는 단일 글자 뒤에 숫자가 오는 형태예요. 두 버전 번호의 숫자 구성 요소가 같다면 프리릴리즈 태그가 있는 쪽이 항상 더 이른(작은) 버전으로 간주돼요. - semver / semantic — 버전 비교에 Semantic Version 체계를 구현해요.
- pep440 — 버전 비교에 파이썬 PEP-440 버저닝 규칙을 구현해요. 2.14 버전에 추가됐어요.
version_type으로 semantic 버전을 비교하려면 다음과 같이 해요.
"{{ sample_semver_var is version('2.0.0-rc.1+build.123', 'lt', version_type='semver') }}"
Ansible 2.14에서 version_type의 pep440 옵션이 추가됐는데, 그 규칙은 PEP-440에 정의돼 있어요. 다음 예시는 이 타입이 프리릴리즈를 일반 릴리즈보다 작은 것으로 구분하는 방법을 보여줘요.
"{{ '2.14.0rc1' is version('2.14.0', 'lt', version_type='pep440') }}"
플레이북이나 역할에서 version을 쓸 때는 FAQ에 설명된 대로 {{ }}를 쓰지 마세요.
vars:
my_version: 1.2.3
tasks:
- debug:
msg: "my_version is higher than 1.0.0"
when: my_version is version('1.0.0', '>')
집합 이론 테스트
2.1 버전에 새로 추가됐어요.
참고: 2.5에서
issubset·issuperset은subset·superset으로 이름이 바뀌었어요.
어떤 목록이 다른 목록을 포함하는지, 또는 다른 목록에 포함되는지 보려면 'subset'과 'superset'을 써요.
vars:
a: [1,2,3,4,5]
b: [2,3]
tasks:
- debug:
msg: "A includes B"
when: a is superset(b)
- debug:
msg: "B is included in A"
when: b is subset(a)
목록이 값을 포함하는지 테스트
2.8 버전에 새로 추가됐어요.
Ansible에는 Jinja2가 제공하는 in 테스트와 비슷하지만 반대로 동작하는 contains 테스트가 있어요. contains 테스트는 select, reject, selectattr, rejectattr 필터와 함께 쓰도록 설계됐어요.
vars:
lacp_groups:
- master: lacp0
network: 10.65.100.0/24
gateway: 10.65.100.1
dns4:
- 10.65.100.10
- 10.65.100.11
interfaces:
- em1
- em2
- master: lacp1
network: 10.65.120.0/24
gateway: 10.65.120.1
dns4:
- 10.65.100.10
- 10.65.100.11
interfaces:
- em3
- em4
tasks:
- debug:
msg: "{{ (lacp_groups|selectattr('interfaces', 'contains', 'em1')|first).master }}"
목록 값이 True인지 테스트
2.4 버전에 새로 추가됐어요.
any와 all을 써서 목록의 요소 중 하나라도(any), 또는 전부(all) 참인지 확인할 수 있어요.
vars:
mylist:
- 1
- "{{ 3 == 3 }}"
- True
myotherlist:
- False
- True
tasks:
- debug:
msg: "all are true!"
when: mylist is all
- debug:
msg: "at least one is true"
when: myotherlist is any
경로 테스트
참고: 2.5에서 다음 테스트들은
is_접두사를 제거하도록 이름이 바뀌었어요.
다음 테스트들은 컨트롤 노드의 경로에 대한 정보를 제공해요.
- debug:
msg: "path is a directory"
when: mypath is directory
- debug:
msg: "path is a file"
when: mypath is file
- debug:
msg: "path is a symlink"
when: mypath is link
- debug:
msg: "path already exists"
when: mypath is exists
- debug:
msg: "path is {{ (mypath is abs)|ternary('absolute','relative')}}"
- debug:
msg: "path is the same file as path2"
when: mypath is same_file(path2)
- debug:
msg: "path is a mount"
when: mypath is mount
- debug:
msg: "path is a directory"
when: mypath is directory
vars:
mypath: /my/path
- debug:
msg: "path is a file"
when: "'/my/path' is file"
크기 형식 테스트
human_readable과 human_to_bytes 함수는 플레이북이 작업에서 올바른 크기 형식을 쓰는지 확인하게 해 줘요. 컴퓨터에는 Byte 형식을, 사람에게는 사람이 읽을 수 있는 형식을 제공하고 있는지 검증하는 거예요.
Human readable
주어진 문자열이 사람이 읽을 수 있는 형식인지 아닌지 단언(assert)해요.
예를 들면,
- name: "Human Readable"
assert:
that:
- '"1.00 Bytes" == 1|human_readable'
- '"1.00 bits" == 1|human_readable(isbits=True)'
- '"10.00 KB" == 10240|human_readable'
- '"97.66 MB" == 102400000|human_readable'
- '"0.10 GB" == 102400000|human_readable(unit="G")'
- '"0.10 Gb" == 102400000|human_readable(isbits=True, unit="G")'
이 결과는 다음과 같아요.
{ "changed": false, "msg": "All assertions passed" }
Human to bytes
주어진 문자열을 Byte 형식으로 돌려줘요.
예를 들면,
- name: "Human to Bytes"
assert:
that:
- "{{'0'|human_to_bytes}} == 0"
- "{{'0.1'|human_to_bytes}} == 0"
- "{{'0.9'|human_to_bytes}} == 1"
- "{{'1'|human_to_bytes}} == 1"
- "{{'10.00 KB'|human_to_bytes}} == 10240"
- "{{ '11 MB'|human_to_bytes}} == 11534336"
- "{{ '1.1 GB'|human_to_bytes}} == 1181116006"
- "{{'10.00 Kb'|human_to_bytes(isbits=True)}} == 10240"
이 결과는 다음과 같아요.
{ "changed": false, "msg": "All assertions passed" }
작업 결과 테스트
다음 작업들은 작업의 상태를 확인하기 위한 테스트들을 보여주는 예시예요.
tasks:
- shell: /usr/bin/foo
register: result
ignore_errors: True
- debug:
msg: "it failed"
when: result is failed
# in most cases you'll want a handler, but if you want to do something right now, this is nice
- debug:
msg: "it changed"
when: result is changed
- debug:
msg: "it succeeded in Ansible >= 2.1"
when: result is succeeded
- debug:
msg: "it succeeded"
when: result is success
- debug:
msg: "it was skipped"
when: result is skipped
참고: 2.1부터 엄격하게 문법을 맞춰야 하는 사람들을 위해
success,failure,change,skip을 쓸 수도 있어요.
타입 테스트
타입을 확인할 때 type_debug 필터를 써서 그 타입의 문자열 이름과 비교하고 싶어질 수 있는데, 대신 타입 테스트 비교를 쓰는 게 좋아요. 예:
tasks:
- name: "String interpretation"
vars:
a_string: "A string"
a_dictionary: {"a": "dictionary"}
a_list: ["a", "list"]
assert:
that:
# Note that a string is classed as also being "iterable" and "sequence", but not "mapping"
- a_string is string and a_string is iterable and a_string is sequence and a_string is not mapping
# Note that a dictionary is classed as not being a "string", but is "iterable", "sequence" and "mapping"
- a_dictionary is not string and a_dictionary is iterable and a_dictionary is mapping
# Note that a list is classed as not being a "string" or "mapping" but is "iterable" and "sequence"
- a_list is not string and a_list is not mapping and a_list is iterable
- name: "Number interpretation"
vars:
a_float: 1.01
a_float_as_string: "1.01"
an_integer: 1
an_integer_as_string: "1"
assert:
that:
# Both a_float and an_integer are "number", but each has their own type as well
- a_float is number and a_float is float
- an_integer is number and an_integer is integer
# Both a_float_as_string and an_integer_as_string are not numbers
- a_float_as_string is not number and a_float_as_string is string
- an_integer_as_string is not number and a_float_as_string is string
# a_float or a_float_as_string when cast to a float and then to a string should match the same value cast only to a string
- a_float | float | string == a_float | string
- a_float_as_string | float | string == a_float_as_string | string
# Likewise an_integer and an_integer_as_string when cast to an integer and then to a string should match the same value cast only to an integer
- an_integer | int | string == an_integer | string
- an_integer_as_string | int | string == an_integer_as_string | string
# However, a_float or a_float_as_string cast as an integer and then a string does not match the same value cast to a string
- a_float | int | string != a_float | string
- a_float_as_string | int | string != a_float_as_string | string
# Again, Likewise an_integer and an_integer_as_string cast as a float and then a string does not match the same value cast to a string
- an_integer | float | string != an_integer | string
- an_integer_as_string | float | string != an_integer_as_string | string
- name: "Native Boolean interpretation"
loop:
- yes
- true
- True
- TRUE
- no
- No
- NO
- false
- False
- FALSE
assert:
that:
# Note that while other values may be cast to boolean values, these are the only ones that are natively considered boolean
# Note also that `yes` is the only case-sensitive variant of these values.
- item is boolean
더 알아보기 (Learn more)
- Ansible playbooks — 플레이북 소개
- Conditionals — 플레이북의 조건문
- Using variables — 변수에 대한 모든 것
- Loops — 플레이북에서 반복하기
- Roles — 역할로 플레이북 구성하기
- General tips — 플레이북 팁과 요령
- Communication — 질문·도움이 필요하면 Ansible 커뮤니케이션 가이드 방문