shell-local post-processor

shell-local post-processor

shell-local post-processor는 post-processing 단계에서 스크립트를 로컬에서 실행해요. Shell local은 packer의 출력물과 변수로 어떤 작업을 실행하는 것을 자동화하는 편리한 방법을 제공해요.

출처: Packer 공식 문서

본문

기본 예시 (Basic example)

아래 예시는 완전히 동작하는 자족적인 빌드예요.

HCL2

source "file" "example" {
    content = "example content"
}

build {
  source "source.file.example" {
    target = "./test_artifact.txt"
  }

  post-processor "shell-local" {
    inline = ["echo foo"]
  }
}

JSON

{
  "builders": [
    {
      "type": "file",
      "name": "example",
      "target": "./test_artifact.txt",
      "content": "example content"
    }
  ],
  "post-processors": [
    {
      "type": "shell-local",
      "inline": ["echo foo"]
    }
  ]
}

구성 레퍼런스 (Configuration Reference)

사용 가능한 구성 옵션의 레퍼런스가 아래에 정리돼 있어요. 필수 요소는 "inline" 또는 "script" 중 하나뿐이고, 다른 모든 옵션은 선택 사항이에요.

정확히 하나 는 필수예요:

  • command (string) - 실행할 단일 명령어. 임시 파일에 기록된 뒤 아래 execute_command 호출로 실행돼요.

  • inline (문자열 배열) - 실행할 명령어들의 배열. 명령어들은 새줄로 연결되어 단일 파일이 되므로 모두 같은 컨텍스트에서 실행돼요. 그래서 한 명령어에서 디렉터리를 바꾸고 다음 명령어에서 그 디렉터리 안의 것을 사용하는 식으로 할 수 있어요. Inline 스크립트는 머신 안에서 간단한 작업을 해내는 가장 쉬운 방법이에요.

  • script (string) - 실행할 스크립트의 경로. 절대 경로나 상대 경로일 수 있어요. 상대 경로라면 Packer가 실행될 때의 작업 디렉터리를 기준으로 해요.

  • scripts (문자열 배열) - 실행할 스크립트들의 배열. 지정된 순서대로 실행돼요. 각 스크립트는 격리된 상태로 실행되므로, 한 스크립트의 변수 같은 상태는 다음 스크립트로 이어지지 않아요.

선택 매개변수:

  • env (문자열 맵) - execute_command 전에 주입할 키/값 쌍의 맵. Packer는 기본적으로도 일부 환경 변수를 환경에 주입하는데, 이는 아래 절에서 다뤄요. 중복된 env 설정은 environment_vars 설정을 재정의해요.

  • environment_vars (문자열 배열) - execute_command 전에 주입할 키/값 쌍의 배열. 형식은 key=value여야 해요. Packer는 기본적으로도 일부 환경 변수를 환경에 주입하는데, 이는 아래 절에서 다뤄요.

  • env_var_format (string) - 여러분이 제공한 environment_vars를 파싱할 때, 환경 변수를 올바르게 설정하도록 보장하는 문자열 템플릿이에요. Windows 호스트에서는 기본적으로 set %s=%s &&이고, Unix에서는 %s='%s'예요. 이 형식을 바꿔야 할 일은 아마 없을 테지만, 필요한 곳의 사용 예시는 아래에서 볼 수 있어요.

  • execute_command (문자열 배열) - 스크립트를 실행하는 데 사용하는 명령어. 기본적으로 *nix 시스템에서는 다음과 같아요:

["/bin/sh", "-c", "{{.Vars}} {{.Script}}"]

Windows에서는 execute_command 기본값이 다음과 같아요:

["cmd", "/V", "/C", "{{.Vars}}", "call", "{{.Script}}"]

이 값은 템플릿 엔진으로 취급돼요. 사용 가능한 변수는 여러 가지가 있어요: Script는 실행할 스크립트의 경로이고, Vars는 설정된 경우 environment_vars의 목록이에요. 추가로 build 템플릿 함수를 사용해 생성된 데이터에 저장된 변수들에 접근할 수 있어요. 이 옵션을 설정하기로 했다면 배열의 첫 번째 요소가 사용하려는 셸 프로그램(예: "sh", "/usr/local/bin/zsh", 심지어 "powershell.exe")인지 확인하세요. 다만 셸 명령어 언어의 한 종류가 아닌 것은 명시적으로 지원되지 않고 Packer 내부의 가정 때문에 깨질 수 있어요. shell-local을 PowerShell이나 다른 Windows 명령어에 사용하려 한다면, 환경 변수가 여러분의 환경에 제대로 설정되지 않을 거라는 점도 알아두세요.

호환성을 위해 execute_command는 문자열 배열 대신 문자열도 받아들여요. 단일 문자열이나 요소가 하나뿐인 문자열 배열이 제공되면, Packer는 ["sh", "-c"] 배열에 여러분의 execute_command를 덧붙여 이전 동작을 재현해요. 예를 들어 "execute_command": "foo bar"로 설정하면 Packer가 실행하는 최종 execute_command는 ["sh", "-c", "foo bar"]예요. "execute_command": ["foo", "bar"]로 설정하면 최종 execute_command는 ["foo", "bar"]로 유지돼요.

다시 말하지만 이것은 호환성 수정으로만 제공되며, execute_command를 문자열 배열로 설정할 것을 강력히 권장해요.

  • inline_shebang (string) - inline으로 지정된 명령어를 실행할 때 사용할 shebang 값. 기본값은 /bin/sh -e예요. inline을 쓰지 않는다면 이 구성은 효과가 없어요. 중요: 이 값을 직접 바꾸면 -e 플래그 같은 것을 꼭 포함하세요. 그렇지 않으면 개별 단계가 실패해도 provisioner가 실패로 처리되지 않아요.

  • keep_input_artifact (boolean) - 대부분의 다른 post-processor와 달리 shell-local post-processor에서는 keep_input_artifact 옵션이 효과가 없어요. shell-local post-processor는 받은 아티팩트를 그저 앞으로 전달하기만 하므로, Packer는 shell-local에 대해 항상 입력 아티팩트를 유지해요. 생성한 파일(들)이 입력 아티팩트를 대체하기를 원한다면, shell-local 프로세서가 실행된 뒤에 artifice post-processor로 입력 아티팩트를 덮어쓰면 돼요.

  • only_on (문자열 배열) - shell-local이 실행될 런타임 운영체제들의 배열. 이 옵션으로 특정 운영체제에서만 shell-local을 실행할 수 있어요. 기본적으로 only_on이 설정되지 않으면 shell-local은 항상 실행돼요.

  • use_linux_pathing (bool) - Windows 호스트에만 관련돼요. Windows Subsystem for Linux 기능이 활성화된 Windows 환경에서 Packer를 실행 중이고, Cmd 스크립트가 아니라 bash 스크립트를 호출하고 싶다면 이 플래그를 true로 설정해야 해요. 그러면 Packer가 Windows 경로가 아니라 Linux 서브시스템 경로를 스크립트에 사용하도록 지시해요. (예: C:/path/to/your/file 대신 /mnt/c/path/to/your/file). 이 기능을 사용하는 방법에 대한 자세한 지침은 아래 예시를 참고해 주세요. Windows 호스트가 아니거나 bash 스크립트를 실행하기 위해 shell-local post-processor를 쓰지 않을 거라면 이 옵션은 무시해 주세요. 이 플래그를 true로 설정해도 script를 제공할 때는 스크립트의 표준 Windows 경로를 제공해야 해요. 이것은 베타 기능이에요.

  • valid_exit_codes (int 목록) - 스크립트의 유효한 종료 코드. 기본값은 0이에요.

실행 명령어 (Execute Command)

많은 새 사용자에게 execute_command는 혼란스러워요. 하지만 여기엔 중요한 기능이 있어요: 명령어가 실행되는 방식을 사용자 정의하는 것. 가장 흔한 사용 사례는 sudo 비밀번호 프롬프트를 다루는 거예요. FreeBSD의 tcsh 같은 비-POSIX 셸을 쓴다면 이것도 사용자 정의해야 할 수 있어요.

Windows Linux Subsystem (The Windows Linux Subsystem)

shell-local post-processor는 로컬 운영체제의 네이티브 셸에서 명령어를 실행하도록 설계됐어요. Windows에서는 기본값이 Cmd라고 가정했어요. 하지만 post-processor 구성에서 execute_command와 use_linux_pathing 옵션을 수정하면 shell-local post-processor에서 Windows Linux Subsystem의 일부로 bash 스크립트를 실행하는 것도 가능해요.

아래 예시는 완전히 동작하는 테스트 구성이에요.

한 가지 제한은 "inline"과 "command" 옵션을 쓸 수 없다는 거예요. "script"나 "scripts" 옵션만 사용해 주세요.

이 기능은 기본 WSL도 여전히 베타이므로 이 기능도 베타 단계라는 점을 알아두세요. 그 결과 몇 가지 제한이 있어요. 예를 들어 Packer와 실행하려는 스크립트가 둘 다 C 드라이브에 있어야 동작할 가능성이 높아요.

HCL2

source "null" "example" {
    communicator = "none"
}

build {
    sources = [
        "source.null.example"
    ]

    post-processor "shell-local"{
        environment_vars  = ["PROVISIONERTEST=ProvisionerTest1"]
        execute_command   = ["bash", "-c", "{{.Vars}} {{.Script}}"]
        use_linux_pathing = true
        scripts           = ["C:/Users/me/scripts/example_bash.sh"]
    }
    post-processor "shell-local"{
        environment_vars  = ["PROVISIONERTEST=ProvisionerTest2"]
        execute_command   = ["bash", "-c", "{{.Vars}} {{.Script}}"]
        use_linux_pathing = true
        script            = "C:/Users/me/scripts/example_bash.sh"
    }
}

JSON

{
  "builders": [
    {
      "type": "null",
      "communicator": "none"
    }
  ],
  "post-processors": [
    {
      "type": "shell-local",
      "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"],
      "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"],
      "use_linux_pathing": true,
      "scripts": ["C:/Users/me/scripts/example_bash.sh"]
    },
    {
      "type": "shell-local",
      "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"],
      "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"],
      "use_linux_pathing": true,
      "script": "C:/Users/me/scripts/example_bash.sh"
    }
  ]
}

기본 환경 변수 (Default Environmental Variables)

environment_vars 구성으로 커스텀 환경 변수를 지정할 수 있을 뿐 아니라, provisioner는 몇 가지 흔히 유용한 환경 변수도 자동으로 정의해요:

  • PACKER_BUILD_NAME은 Packer가 실행 중인 빌드의 이름으로 설정돼요. Packer가 여러 빌드를 만들 때 공통 프로비저닝 스크립트에서 그것들을 조금 구분하고 싶을 때 가장 유용해요.

  • PACKER_BUILDER_TYPE은 스크립트가 실행 중인 머신을 만드는 데 사용된 builder의 타입이에요. 특정 builder로 만든 시스템에서 스크립트의 일부만 실행하고 싶을 때 유용해요.

스크립트 안전하게 작성하기 (Safely Writing A Script)

inline 옵션을 쓰든, 직접 script나 scripts를 전달하든, shell-local post-processor가 어떻게 동작하는지 몇 가지를 이해하는 게 안전하고 쉽게 실행하는 데 중요해요. 이 이해는 작업 시간을 크게 아껴줄 거예요.

builder당 한 번 (Once Per Builder)

전달하는 shell-local 스크립트(들)은 builder당 한 번 실행돼요. 즉 amazon-ebs builder와 docker builder가 있다면 스크립트가 두 번 실행돼요. builder가 3개면 각 builder마다 총 3번 실행돼요.

빌드 아티팩트와 상호작용 (Interacting with Build Artifacts)

빌드 아티팩트와 상호작용하려면 manifest post-processor를 사용하면 돼요. 이 post-processor는 각 builder가 실행된 후 builder가 만든 파일 목록을 json 파일에 기록해요.

예를 들어 file builder의 파일을 tarball로 패키징하고 싶다면 이렇게 쓸 수 있어요:

JSON

{
  "builders": [
    {
      "content": "Lorem ipsum dolor sit amet",
      "target": "dummy_artifact",
      "type": "file"
    }
  ],
  "post-processors": [
    [
      {
        "output": "manifest.json",
        "strip_path": true,
        "type": "manifest"
      },
      {
        "inline": [
          "jq \".builds[].files[].name\" manifest.json | xargs tar cfz artifacts.tgz"
        ],
        "type": "shell-local"
      }
    ]
  ]
}

HCL2

source "file" "example" {
    content = "Lorem ipsum dolor sit amet"
    target  = "dummy_artifact.txt"
}
build {
  sources = [
    "source.file.example"
  ]
  post-processor "manifest" {
    output     = "manifest.json"
    strip_path = true
  }

  post-processor "shell-local" {
    inline = [
        "jq \".builds[].files[].name\" manifest.json | xargs tar cfz artifacts.tgz"
    ]
  }
}

이 예시는 jq 도구로 manifest 파일에서 모든 파일 이름을 추출해 tar에 전달해요.

항상 의도적으로 종료하기 (Always Exit Intentionally)

어떤 post-processor든 실패하면 packer build는 멈추고 모든 중간 아티팩트를 정리해요.

셸 스크립트의 경우 스크립트가 반드시 0 코드로 종료해야 한다는 뜻이에요. 필요할 때 exit 0을 하는 데 매우 주의해야 해요.

사용 예시 (Usage Examples)

Windows 호스트 (Windows Host)

Windows에서 .cmd 파일을 실행하는 예시:

HCL2

post-processor "shell-local" {
  environment_vars = ["SHELLLOCALTEST=ShellTest1"]
  scripts          = ["./scripts/test_cmd.cmd"]
}

JSON

{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest1"],
  "scripts": ["./scripts/test_cmd.cmd"]
}

test_cmd.cmd의 내용:

echo %SHELLLOCALTEST%

Windows에서 inline 명령어를 실행하는 예시: 필요한 사용자 정의는 tempfile_extension이에요.

HCL2

post-processor "shell-local" {
  environment_vars   = ["SHELLLOCALTEST=ShellTest2"],
  tempfile_extension = ".cmd",
  inline             = ["echo %SHELLLOCALTEST%"]
}

JSON

{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest2"],
  "tempfile_extension": ".cmd",
  "inline": ["echo %SHELLLOCALTEST%"]
}

WSL을 사용해 Windows에서 bash 명령어를 실행하는 예시: 필요한 사용자 정의는 use_linux_pathing과 execute_command예요.

HCL2

post-processor "shell-local" {
  environment_vars  = ["SHELLLOCALTEST=ShellTest3"],
  execute_command   = ["bash", "-c", "{{.Vars}} {{.Script}}"]
  use_linux_pathing = true
  script            = "./scripts/example_bash.sh"
}

JSON

{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest3"],
  "execute_command": ["bash", "-c", "{{.Vars}} {{.Script}}"],
  "use_linux_pathing": true,
  "script": "./scripts/example_bash.sh"
}

example_bash.sh의 내용:

#!/bin/bash
echo $SHELLLOCALTEST

Windows에서 PowerShell 스크립트를 실행하는 예시: 필요한 사용자 정의는 env_var_format과 execute_command예요.

HCL2

post-processor "shell-local" {
  environment_vars = ["SHELLLOCALTEST=ShellTest4"]
  execute_command  = ["powershell.exe", "{{.Vars}} {{.Script}}"]
  env_var_format   = "$env:%s=\"%s\"; "
  script           = "./scripts/example_ps.ps1"
}

JSON

{
  "type": "shell-local",
  "environment_vars": ["SHELLLOCALTEST=ShellTest4"],
  "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"],
  "env_var_format": "$env:%s=\"%s\"; ",
  "script": "./scripts/example_ps.ps1"
}

Windows에서 PowerShell 스크립트를 "inline"으로 실행하는 예시: 필요한 사용자 정의는 env_var_format, tempfile_extension, execute_command예요.

HCL2

post-processor "shell-local" {
  tempfile_extension = ".ps1"
  environment_vars   = ["SHELLLOCALTEST=ShellTest5"]
  execute_command    = ["powershell.exe", "{{.Vars}} {{.Script}}"]
  env_var_format     = "$env:%s=\"%s\"; "
  inline             = ["write-output $env:SHELLLOCALTEST"]
}

JSON

{
  "type": "shell-local",
  "tempfile_extension": ".ps1",
  "environment_vars": ["SHELLLOCALTEST=ShellTest5"],
  "execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"],
  "env_var_format": "$env:%s=\"%s\"; ",
  "inline": ["write-output $env:SHELLLOCALTEST"]
}

Unix 호스트 (Unix Host)

Unix에서 Shell 스크립트를 실행하는 예시:

HCL2

post-processor "shell-local" {
  environment_vars = ["PROVISIONERTEST=ProvisionerTest1"]
  scripts = ["./scripts/example_bash.sh"]
}

JSON

{
  "type": "shell-local",
  "environment_vars": ["PROVISIONERTEST=ProvisionerTest1"],
  "scripts": ["./scripts/example_bash.sh"]
}

Unix에서 bash "inline"을 실행하는 예시:

HCL2

post-processor "shell-local" {
  environment_vars = ["PROVISIONERTEST=ProvisionerTest2"]
  inline           = ["echo hello", "echo $PROVISIONERTEST"]
}

JSON

{
  "type": "shell-local",
  "environment_vars": ["PROVISIONERTEST=ProvisionerTest2"],
  "inline": ["echo hello", "echo $PROVISIONERTEST"]
}

Unix에서 Python 스크립트를 실행하는 예시:

HCL2

post-processor "shell-local" {
  script           = "hello.py"
  environment_vars = ["HELLO_USER=packeruser"]
  execute_command  = [
    "/bin/sh",
    "-c",
    "{{.Vars}} /usr/local/bin/python {{.Script}}"
  ]
}

JSON

{
  "type": "shell-local",
  "script": "hello.py",
  "environment_vars": ["HELLO_USER=packeruser"],
  "execute_command": [
    "/bin/sh",
    "-c",
    "{{.Vars}} /usr/local/bin/python {{.Script}}"
  ]
}

여기서 "hello.py"는 다음을 담고 있어요:

import os

print('Hello, %s!' % os.getenv("HELLO_USER"))