반응형
반응형

python도 그렇고, windows도 그렇고 escape처리가 다른 언어 및 OS에 비해 까다롭기 때문에, win_command 처럼 문자열을 많이 입력해야 하는 모듈의 경우 escape 처리에 신경을 많이 써야 한다.

 

msi 파일 등을 무인 설치 하고, 설치된 디렉토리에 가서 뭔가 명령어를 입력해야 하는 경우, C:\Program Files 같이 중간에 띄어쓰기가 있는 경우 최근에 삽집을 했기 때문에 해당 내용에 대해 정리함.

 

C:\Program Files\Abc eef 경로에 있는 execute.cmd를 -r argument까지 넣어서 실행해야 하는 경우 아래와 같이 하면 문제가 없다. (참고로 win_shell 모듈 사용의 경우에는 powershell 기반이라 그런지 첫 글자에 \가 붙어서 문제가 발생하게 된다.)

---
 - name: test escape string
   win_command: '"C:\Program Files\Abc eef\execute.cmd" -r'

 

다음 예시로, 위와 동일한 경로에서 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)를 뽑아낼 수 있다는 내용이다.

https://www.scconfigmgr.com/2014/08/22/how-to-get-msi-file-information-with-powershell/ 

 

How to get MSI file information with PowerShell

If you’re an ConfigMgr administrator like me, you will often find yourself in the situation where you may want to get the Product Code from a MSI file. Or perhaps you’re interested in the Product Version or simply just the Product Name. A while back I wrot

www.scconfigmgr.com

 

혹시 위 링크의 정보가 사라질 수 있으므로, Powershell 스크립트와 실행법에 대해 이 글에 복붙을 해보기로 한다.

param(
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [System.IO.FileInfo]$Path,
 
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [ValidateSet("ProductCode", "ProductVersion", "ProductName", "Manufacturer", "ProductLanguage", "FullVersion")]
    [string]$Property
)
Process {
    try {
        # Read property from MSI database
        $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
        $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0))
        $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'"
        $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query))
        $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
        $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
        $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)
 
        # Commit database and close view
        $MSIDatabase.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDatabase, $null)
        $View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null)           
        $MSIDatabase = $null
        $View = $null
 
        # Return the value
        return $Value
    } 
    catch {
        Write-Warning -Message $_.Exception.Message ; break
    }
}
End {
    # Run garbage collection and release ComObject
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WindowsInstaller) | Out-Null
    [System.GC]::Collect()
}

 

실행법은 파워쉘을 연 후에 다음과 같은 커맨드를 입력하면 된다.


.\파일명.ps1 -Path "MSI파일의 경로" -Property ProductCode

위의 powershell 스크립트를 이용해서 win_package 모듈 사용 시 아래와 같이 product_id를 추가해주면 에러가 사라지게 된다.

...
 - name: win_package test
   win_package:
     path: MSI파일을 받을 URL 입력
     product_id: Powershell 스크립트를 이용해 얻은 product_id (single quote를 붙여주자.)
     arguments:
       - /quiet
...

 

반응형
,
반응형

우선 위 에러가 발생하는 경우는 playbook이든 ansible이든 become 메소드를 이용하여 sudo 권한을 획득하려 했으나, Windows 머신 등이 inventory에 포함되어 있는 경우 sudo 가 없기 때문에 발생한다.

 

Windows 머신의 경우 become 메소드 없이도, Administrator 계정을 inventory에 넣으면 비슷한 효과가 있기 때문에 inventory에서 become 메소드를 아예 활용하지 않도록 아래와 같이 예외처리가 가능하다.

[windows:vars]
ansible_become=false

[windows]
localhost1
localhost2
localhost3

 

ansible_become을 inventory에 선언해두면, playbook에서 become이 yes로 되어 있더라도, 해당 host들은 inventory에 선언해둔 값을 우선 시 하기 때문에, 문제가 발생하지 않는다.

반응형
,
반응형

playbook 작성 시 상단에 gather_facts 여부를 지정할 수가 있다.

playbook 하나로 모든 OS에 대해서 대응을 해야 하는 경우, OS에 대한 종류가 많아 분기문이 많이 들어가야 하는 경우에 ansible_facts를 사용해서 해당 하는 서버의 내부 정보를 수집하고, 그 수집된 정보 중 OS의 정보를 가져와서 해당 값들을 분기문에 추가할 수가 있다.

이런 경우 gather_facts를 yes로 지정해 주어야 한다.

 

분기문에 추가하는 경우는 아래와 같이 심플하게 추가할 수 있다.

---
 - hosts: all
   gather_facts: yes
   tasks:
     - name: check ubuntu14
       debug:
         msg: "ubuntu 14!!!"
       when:
         - ansible_facts['distribution_file_variety'] == "Debian"
         - ansible_facts['distribution_major_version'] == "14"

 

inventory에 linux 장비들만 포함되어 있다면 크게 문제가 없이 실행이 된다.

하지만, 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"

 

 

반응형
,
반응형

AWS에서 EC2를 이용하는 경우, 기본적으로 SSH접근은 private pem file을 이용하여 접근을 하는데, Ansible에서는 pem file을 어떻게 등록할까를 알아보다가 적당한 옵션을 찾게 되어 포스팅한다.

[ec2:vars]
ansible_ssh_private_key_file=/etc/ansible/aws-test.pem
ansible_user=ubuntu

[ec2]
41.123.123.123
51.213.123.123


이제 password 기반 및 pem key 기반에서도 ansible을 이용할 수 있게 되었다!

반응형
,
반응형

Ansible은 기본적으로 ssh를 기반으로 하고 있고, 별다른 옵션을 지정해주지 않으면 root 계정을 활용해서 통신을 한다.


하지만, 보통 root 계정으로 직접 접근하는 경우는 드물고, 공용 계정을 하나 만들어 sudo 로 전환해서 사용을 하는데, 이런 경우 Ansible에 inventory를 등록 시 아래와 같이 ansible_user라는 옵션을 주어야 한다.

[nodes]
192.168.0.1 ansible_user=hakurei
192.168.0.2 ansible_user=hakurei
192.168.0.3 ansible_user=hakurei


근데 만약 host가 100대가 넘어가는 경우, 매 라인마다 저걸 넣어줄 수는 없으므로, 아래와 같이 :vars라는 옵션을 이용하여 공통화가 가능하다.

[nodes:vars]
ansible_user=hakurei

[nodes]
192.168.0.1
192.168.0.2
192.168.0.3


반응형
,
반응형