[Ansible] 앤서블 Jinja2 필터 사용법

[Ansible] 앤서블 Jinja2 필터 사용법

안녕하세요? 정리하는 개발자 워니즈 입니다. 필자가 갑자기 Ansible을 너무 재밌게 하고 있어서 당분간 Ansible 연재를 계속 할 것 같습니다. 지난시간까지는 간단한 playbook 사용법에 대해서 알아봤는데요. 이번시간에는 데이터 변경작업에 대한 이야기를 해보려고 합니다.

Ansible은 보면 템플릿을 잘 활용해야되는데요. 그안에서 데이터를 적절히 조작해서 필요한 작업을 진행할 수 있습니다.

Jinja2 필터는 템플릿에서 데이터를 어떤 형태에서 다른 형태로 바꾸는 작업입니다. 이미 많은 내장 함수를 가지고 있습니다. 이런 필터 기능은 로컬 데이터를 다루는 Ansible의 컨트롤러에서 수행되는 것이며 타겟 머신의 태스크에서 이루어 지는 것이 아닙니다.

1. 기본 예제

  • 데이터 포맷을 위한 필터

어떤 결과를 다른 형태로 변경하는 것입니다. 디버깅을 할 때 유용한 경우가 있습니다.

{{ some_variable | to_json }}
{{ some_variable | to_yaml }}

또는 사람이 읽기 쉬운 형태로 출력을 합니다.

{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}

파이썬의 pprint 모듈처럼 indent를 줄 수도 있습니다. (버전 2.2 이후)

{{ some_variable | to_nice_json(indent=2) }}
{{ some_variable | to_nice_yaml(indent=8) }}

이제는 반대로 이미 포맷된 것에서 읽어 오는 경우도 있습니다.

{{ some_variable | from_json }}
{{ some_variable | from_yaml }}

예시

tasks:
  - shell: cat /some/path/to/file.json
    register: result

  - set_fact: myvar="{{ result.stdout | from_json }}"
  • 어떤 변수가 정의되어야만 하도록 강제함

어떤 변수가 정의되어 있지 않다면 ansible의 ansible.cfg에 정의되어 있는 디폴트 행동을 따르게 되어 있는데 이를 끌 수 있습니다.다음은 그 기능이 꺼지고 꼭 해당 변수가 존재하는지 체크하도록 하는 것입니다.

{{ variable | mandatory }}

만약 해당 변수가 정의되어 있지 않다면 템플릿이 동작하면서 오류가 발생합니다.

  • 정의 되지 않은 변수의 디폴트 값 정의

Jinja2 는 default 필터를 제공하는데 해당 변수가 정의되지 않을 경우 디폴트 값을 갖게 됩니다.

{{ some_variable | default(5) }}
  • 정의되지 않은 변수와 패러미터의 생략

버전 1.8 부터 omit이라는 특별 변수를 사용하여 변수나 모듈 패러미터를 생략하기위한 디폴트 필터를 사용할 수 있습니다.

- name: touch files with an optional mode
  file: dest={{item.path}} state=touch mode={{item.mode|default(omit)}}
  with_items:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"

파일경로 path가 3개 있는데 앞에 2개는 시스템에서 제공하는 umask에 따라 touch 되고 마지막 파일경로 /tmp/baz 는 시스템 디폴트 umask와 상관없이 0444 로 설정됩니다.

2. 데이터 필터

  • List 필터

버전 1.8 이후.

{{ list1 | min }}
{{ [3, 4, 2] | max }}
  • 집합(SET) 필터

버전 1.4 이후.

#중복 제외
{{ list1 | unique }}    

#합집합
{{ list1 | union(list2) }}

#교집합
{{ list1 | intersect(list2) }}

#차집합
{{ list1 | difference(list2) }}

#합집합 - 교집합
{{ list1 | symmetric_difference(list2) }}
  • 랜덤 필터

버전 1.6 이후.

#임의항목 추출
{{ ['a','b','c']|random }} => 'c'

#0-100 사이의 숫자중 10단위 숫자
{{ 100 |random(step=10) }}  => 70

#10씩 증가하는 숫자 구하기
{{ 100 |random(1, 10) }}    => 31
{{ 100 |random(start=1, step=10) }}    => 51

#셔플 필터
{{ ['a','b','c']|shuffle }} => ['c','a','b']
{{ ['a','b','c']|shuffle }} => ['b','c','a']
  • Math 필터

버전 1.9 이후.

#로그값 구하기
{{ myvar | log }}

#제곱값 구하기
{{ myvar | pow(2) }}
{{ myvar | pow(5) }}

#제곱근 구하기
{{ myvar | root }}
{{ myvar | root(5) }}
  • IP 주소 필터

버전 1.9 이후.

{{ myvar | ipaddr }}

#ip 주소를 버전 4,6으로 지정할 때
{{ myvar | ipv4 }}
{{ myvar | ipv6 }}
{{ '192.0.2.1/24' | ipaddr('address') }}

더 자세한 ipaddr 기능은 Jinja2 ipaddr() 필터를 참조하십시오.

  • 유용한 필터
#쉘에서 따옴표 추가
- shell: echo {{ string_value | quote }}

#조건식
{{ (name == "John") | ternary('Mr','Ms') }}

#하나의 문자열로 만들기
{{ list | join(" ") }}

#파일의 경로명 `/etc/asdf/foo.txt`에서 파일명 `foo.txt` 만 추출하려면
{{ path | basename }}

#파일 경로에서 디렉토리만 구하기
{{ path | dirname }}

#링크의 실제 경로
{{ path | realpath }}

#주어진 경로의 상대 경로
{{ path | relpath('/etc') }}

#확장자 분리
# with path == 'nginx.conf' the return would be ('nginx', '.conf')
{{ path | splitext }}

#인코딩 문자열 구하기
{{ encoded | b64decode }}
{{ decoded | b64encode }}

#정규 표현식
# convert "ansible" to "able"
{{ 'ansible' | regex_replace('^a.*i(.*)$', 'a\\1') }}

# convert "foobar" to "bar"
{{ 'foobar' | regex_replace('^f.*o(.*)$', '\\1') }}

# convert "localhost:80" to "localhost, 80" using named groups
{{ 'localhost:80' | regex_replace('^(?P<host>.+):(?P<port>\\d+)$', '\\g<host>, \\g<port>') }}

3. Jinja2 테스트 관련 필터

Jinja2의 테스트는 템플릿을 돌려 True 또는 False 결과를 리턴하는가를 보고 판단합니다. Jinja2의 내장 테스트 문서를 참조합니다. 테스트 하는 것은 필터를 이용하는 것과 동일하지만 C(map()) 또는 C(select()) 와 같이 목록에서 항목을 선택하는 것과 같은 목록 처리 필터에도 사용될 수 있습니다.

필터와 마찬가지로 테스트도 로컬 데이터를 테스트하는 Ansible 컨트롤러에서 수행되며, 원격 대상 태스크에서 수행되지는 않습니다.

3-1. 문자열 테스트

vars:
  url: "http://example.com/users/foo/resources/bar"

tasks:
    - shell: "msg='matched pattern 1'"
      when: url | match("http://example.com/users/.*/resources/.*")

    - debug: "msg='matched pattern 2'"
      when: url | search("/users/.*/resources/.*")

    - debug: "msg='matched pattern 3'"
      when: url | search("/users/")

match는 전체 문자열에 대한 매치를 하는 반면 search는 부분 매치를 합니다.

3-2. 그룹 포함 테스트

어떤 그룹이 다른 그룹의 서브셑 또는 수퍼셑 인가를 체크하기 위한 issubset, issuperset 필터가 있습니다.

vars:
    a: [1,2,3,4,5]
    b: [2,3]
tasks:
    - debug: msg="A includes B"
      when: a|issuperset(b)

    - debug: msg="B is included in A"
      when: b|issubset(a)

3-3. 경로 테스트

- debug: msg="path is a directory"
  when: mypath|isdir

- 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|exists

- debug: msg="path is {{ (mypath|is_abs)|ternary('absolute','relative')}}"

- debug: msg="path is the same file as path2"
  when: mypath|samefile(path2)

- debug: msg="path is a mount"
  when: mypath|ismount

3-4. 태스트 결과 테스트

tasks:

  - shell: /usr/bin/foo
    register: result
    ignore_errors: True

  - debug: msg="it failed"
    when: result|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|changed

  - debug: msg="it succeeded in Ansible >= 2.1"
    when: result|succeeded

  - debug: msg="it succeeded"
    when: result|success

  - debug: msg="it was skipped"
    when: result|skipped

4. 마치며

이번시간에는 앤서블의 Jinja2 필터 사용법에 대해서 알아봤습니다. 대부분의 playbook 예시에서 Jinja2 필터는 상당히 많이 사용되고 있습니다. 기존에 개발자가 코딩으로 만들던 부분을 내장함수를 통해서 손쉽게 데이터를 변환하고 조작할 수 있기에 유용합니다.

다음 이시간에는 조건식/반복문 에 대해서 정리해보도록 하겠습니다.

워니즈 블로그
워니즈 깃헙

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다