python도 그렇고, windows도 그렇고 escape처리가 다른 언어 및 OS에 비해 까다롭기 때문에, win_command 처럼 문자열을 많이 입력해야 하는 모듈의 경우 escape 처리에 신경을 많이 써야 한다.
msi 파일 등을 무인 설치 하고, 설치된 디렉토리에 가서 뭔가 명령어를 입력해야 하는 경우, C:\Program Files 같이 중간에 띄어쓰기가 있는 경우 최근에 삽집을 했기 때문에 해당 내용에 대해 정리함.
C:\Program Files\Abc eef 경로에 있는 execute.cmd를 -r argument까지 넣어서 실행해야 하는 경우 아래와 같이 하면 문제가 없다. (참고로 win_shell 모듈 사용의 경우에는 powershell 기반이라 그런지 첫 글자에 \가 붙어서 문제가 발생하게 된다.)
다음 예시로, 위와 동일한 경로에서 argument까지 넣어서 실행 한 후 추가 argument에 ansible_fact에서 가져온 정보까지 넣어야 하는 경우 아래와 같이 하면 된다.
---
- name: test escape string with ansible_facts
win_command: '"C:\Program Files\Abc eef\execute.cmd" -r {{ ansible_facts["hostname"] }}'
두 개의 예시를 보면 알겠지만, 띄어쓰기가 포함된 경로는 Double quote로 묶어주는 것이 핵심이고, 경로만 묶는 것이 아닌 실행파일까지 한 번에 묶는 것이 중요하다. 그리고 전체 커맨드를 Single quote로 묶어주는 것도 중요하며, ansible_facts를 기본적으로 Single quote로 많이 사용하는 데, 전체 커맨드를 Single quote로 묶어준 경우 Double quote로 바꿔서 사용해도 문제가 없다.
Ansible의 Windows 모듈 중에 win_package에서는 특정 URL에서 msi 파일이나 exe 파일을 다운받아서 설치하는 과정까지 한 번에 해주는 기능을 제공한다.
아래와 같이 작성하면 일반적으로 문제가 없을 거라고 생각했다.
...
- name: win_package test
win_package:
path: MSI파일을 받을 URL 입력
arguments:
- /quiet
...
실행해보면 아래와 같은 Fatal 에러가 발생한다.
fatal: [x.x.x.x]: FAILED! => {"changed": false, "msg": "product_id is required when the path is not an MSI or the path is an MSI but not local", "reboot_required": false, "restart_required": false}
MSI파일이 로컬 내에 위치해 있지 않은 경우 product_id를 넣으라는 의미인데..
Windows를 잘 모르는 내 입장에서 product_id를 어떻게 찾아야 할 지 막막해서 구글링을 해본 결과 다음과 같은 링크를 찾게 됨.
powershell을 이용해서 msi 파일의 정보를 뽑아내는데, 그 중에 ProductCode (ansible에서 이야기하는 product_id)를 뽑아낼 수 있다는 내용이다.
위의 powershell 스크립트를 이용해서 win_package 모듈 사용 시 아래와 같이 product_id를 추가해주면 에러가 사라지게 된다.
...
- name: win_package test
win_package:
path: MSI파일을 받을 URL 입력
product_id: Powershell 스크립트를 이용해 얻은 product_id (single quote를 붙여주자.)
arguments:
- /quiet
...
playbook 하나로 모든 OS에 대해서 대응을 해야 하는 경우, OS에 대한 종류가 많아 분기문이 많이 들어가야 하는 경우에 ansible_facts를 사용해서 해당 하는 서버의 내부 정보를 수집하고, 그 수집된 정보 중 OS의 정보를 가져와서 해당 값들을 분기문에 추가할 수가 있다.
하지만, windows 머신이 inventory에 하나라도 포함되어 있다면, gather_facts시 linux 에서만 수집할 수 있는 ansible_facts['distribution_file_variety'] 값이 없기 때문에 에러가 발생할 수 있다.
이런 경우에는 when 문에 아래와 같이 is defined 문을 추가해줌으로써 해결할 수 있다.
---
- hosts: all
gather_facts: yes
tasks:
- name: check ubuntu14
debug:
msg: "ubuntu 14!!!"
when:
- ansible_facts['distribution_file_variety'] is defined
- ansible_facts['distribution_file_variety'] == "Debian"
- ansible_facts['distribution_major_version'] == "14"
일반적인 프로그래밍 언어의 if문과 같은 매커니즘 이므로, is defined 문은 가장 상단에 위치해야 원하는대로 동작하게 된다.
이제 facts를 수집해서 분기문을 처리하는 부분까지는 완료를 했으나, 수집된 facts를 msg 등의 string에 포함시켜서 출력해야 하는 경우가 있을 수 있는데, 이런 경우 ansible에서는 jinja2 엔진을 내장하고 있기 때문에 jinja2 문법을 활용하면 된다.
아래는 debug모듈의 msg에 ansible에서 수집한 facts 값을 출력하는 예시를 들어보았다.
---
- hosts: all
gather_facts: yes
tasks:
- name: check ubuntu14
debug:
msg: "ubuntu 14!!! file_variety is {{ ansible_facts['distribution_file_variety'] }} !!"
when:
- ansible_facts['distribution_file_variety'] is defined
- ansible_facts['distribution_file_variety'] == "Debian"
- ansible_facts['distribution_major_version'] == "14"