AWS-ApplyAnsiblePlaybooksをAmazon Linux 2023でも使えるようにする。

# はじめに
先月あたりに`Amazon Linux 2023`が登場しましたが、`Ansible`が実行できる`Systems Manager Run Command`の`AWS-ApplyAnsiblePlaybooks`がまだ`Amazon Linux 2023`に対応していなかったため、`Amazon Linux 2023`でも動かせるようにしたいと思います。

# Systems Manager Run CommandによるAnsibleの実行
過去記事で何度か紹介しているため、詳しくは以下を参照。

- [Amazon S3からのスクリプトの実行をやってみた。](https://qiita.com/sakai00kou/items/326a36fbdea10a6b2da8)
- [AnsibleをAWS Systems Managerから実行するCI/CDを構築する。（その１：Systems ManagerからのAnsible実行）](https://qiita.com/sakai00kou/items/5604b94dd2c87922538f)

# Systems Manager Run Commandについて
`Systems Manager Run Command`は予め準備されている「**ドキュメント**」と呼ばれる定義ファイルに従って、指定したインスタンスに対してコマンド等を実行する機能となります。

`Ansible`を実行するAWS公式の`AWS-ApplyAnsiblePlaybooks`ドキュメントもドキュメント内の設定を見ると分かる通り、やっていることは`Ansible`のインストールと`playbook`の実行、あとはAWSマネジメントコンソールからの操作時に表示する設定が書かれているだけなので、このドキュメントをカスタマイズして、`Amazon Linux 2023`でも使えるようにします。

# なぜAmazon Linux 2023のインスタンスに対して実行できないか？
実際に実行してみれば分かりますが、`AWS-ApplyAnsiblePlaybooks`ドキュメントに書かれている条件式が`Amazon Linux 2023`に対応していないからとなります。

以下ドキュメントからの条件式部分抜粋ですが、インスタンスのOS判定のため、`/etc/system-release`というファイルの中身を見てOSを判定し、OS毎に`Ansible`のインストール方法を変えています。

```json:OS判定条件式抜粋
        "runCommand": [
          "#!/bin/bash",
          "if  [[ \"{{InstallDependencies}}\" == True ]] ; then",
          "   echo \"Installing and or updating required tools: Ansible, wget unzip ....\" >&2",
          "   if [ -f  \"/etc/system-release\" ] ; then",
          "     if cat /etc/system-release|grep -i 'Amazon Linux release 2' ; then ",
          "       sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm",
          "       sudo yum install -y ansible",
          "       sudo yum -y install unzip",
          "     elif cat /etc/system-release|grep -i 'Amazon Linux AMI' ; then ",
          "       sudo pip install ansible --upgrade",
          "       sudo yum -y install unzip",
          "     elif cat /etc/system-release|grep -i 'Red Hat Enterprise Linux' ; then ",
          "       sudo yum -y install python3-pip",
          "       sudo pip3 install ansible",
          "       sudo yum -y install unzip",
          "     else",
          "       echo \"There was a problem installing or updating the required tools for the document. You can review the log files to help you correct the problem.\" >&2",
          "       exit 1",
          "     fi",
```

ちなみに`Amazon Linux 2023`の`/etc/system-release`には以下のような文言が記載されております。

```bash:Amazon Linux 2023の/etc/system-release
Amazon Linux release 2023 (Amazon Linux)
```

`Amazon Linux 2023`のインスタンスで実行した場合、Amazon Linux 2用の`Amazon Linux release 2`に引っかかるため、`epel`リポジトリインストールして`Ansible`をインストールする流れとなりますが、`Amazon Linux 2023`のリポジトリには`epel`リポジトリが無いことから`Ansible`のインストールに失敗してしまうことが原因となります。

# カスタムドキュメント作成
以下より`Amazon Linux 2023`対応したカスタムドキュメントを作成していこうと思います。

「**AWS Systems Manager**」の左ペインから「**ドキュメント**」を選択し、「**AWS-ApplyAnsiblePlaybooks**」を選びます。

右上の「**アクション**」から「**ドキュメントのクローン作成**」を選択するとクローンが作成できるため、以下のように入力。

尚、名前については先頭が`AWS`で始まる名前は予約されており、指定できないので、今回は`Custom-ApplyAnsiblePlaybooksAL2023`といった名前にしました。

|項目|設定|備考|
|:--|:--|:--|
|名前|Custom-ApplyAnsiblePlaybooksAL2023|適当な名前を入力|
|ターゲットタイプ|空欄||
|ドキュメントタイプ|コマンドドキュメント|クローン作成の場合は指定不可|
|コンテンツ|JSON|コンテンツの中身については後述|
|ドキュメントのタグ|任意||

## Amazon Linux 2023対応版コンテンツの作成
先に対応版のドキュメント全容を紹介。

```json:Custom-ApplyAnsiblePlaybooksAL2023
{
  "schemaVersion": "2.2",
  "description": "Use this document to run Ansible Playbooks on Systems Manager managed instances. For more information, see https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-state-manager-ansible.html.",
  "parameters": {
    "SourceType": {
      "description": "(Optional) Specify the source type.",
      "type": "String",
      "allowedValues": [
        "GitHub",
        "S3"
      ]
    },
    "SourceInfo": {
      "description": "(Optional) Specify the information required to access the resource from the specified source type. If source type is GitHub, then you can specify any of the following: 'owner', 'repository', 'path', 'getOptions', 'tokenInfo'. Example GitHub parameters: {\"owner\":\"awslabs\",\"repository\":\"amazon-ssm\",\"path\":\"Compliance/InSpec/PortCheck\",\"getOptions\":\"branch:master\"}. If source type is S3, then you can specify 'path'. Important: If you specify S3, then the IAM instance profile on your managed instances must be configured with read access to Amazon S3.",
      "type": "StringMap",
      "displayType": "textarea",
      "default": {}
    },
    "InstallDependencies": {
      "type": "String",
      "description": "(Required) If set to True, Systems Manager installs Ansible and its dependencies, including Python, from the PyPI repo. If set to False, then verify that Ansible and its dependencies are installed on the target instances. If they aren’t, the SSM document fails to run.",
      "allowedValues": [
        "True",
        "False"
      ],
      "default": "True"
    },
    "PlaybookFile": {
      "type": "String",
      "description": "(Optional) The Playbook file to run (including relative path). If the main Playbook file is located in the ./automation directory, then specify automation/playbook.yml.",
      "default": "hello-world-playbook.yml",
      "allowedPattern": "[(a-z_A-Z0-9\\-\\.)/]+(.yml|.yaml)$"
    },
    "ExtraVariables": {
      "type": "String",
      "description": "(Optional) Additional variables to pass to Ansible at runtime. Enter key/value pairs separated by a space. For example: color=red flavor=cherry",
      "default": "SSM=True",
      "displayType": "textarea",
      "allowedPattern": "^$|^\\w+\\=(([^\\s|:();&]+)|('[^|:();&]+'))(\\s+\\w+\\=(([^\\s|:();&]+)|('[^|:();&]+')))*$"
    },
    "Check": {
      "type": "String",
      "description": "(Optional) Use this parameter to run a check of the Ansible execution. The system doesn’t make any changes to your systems. Instead, any module that supports check mode reports the changes it would make rather than making them. Modules that don’t support check mode take no action and don’t report changes that would be made.",
      "allowedValues": [
        "True",
        "False"
      ],
      "default": "False"
    },
    "Verbose": {
      "type": "String",
      "description": "(Optional) Set the verbosity level for logging Playbook executions. Specify -v for low verbosity, -vv or –vvv for medium verbosity, and -vvvv for debug level.",
      "allowedValues": [
        "-v",
        "-vv",
        "-vvv",
        "-vvvv"
      ],
      "default": "-v"
    },
    "TimeoutSeconds": {
      "type": "String",
      "description": "(Optional) The time in seconds for a command to be completed before it is considered to have failed.",
      "default": "3600"
    }
  },
  "mainSteps": [
    {
      "action": "aws:downloadContent",
      "name": "downloadContent",
      "inputs": {
        "SourceType": "{{ SourceType }}",
        "SourceInfo": "{{ SourceInfo }}"
      }
    },
    {
      "action": "aws:runShellScript",
      "name": "runShellScript",
      "inputs": {
        "timeoutSeconds": "{{ TimeoutSeconds }}",
        "runCommand": [
          "#!/bin/bash",
          "if  [[ \"{{InstallDependencies}}\" == True ]] ; then",
          "   echo \"Installing and or updating required tools: Ansible, wget unzip ....\" >&2",
          "   if [ -f  \"/etc/system-release\" ] ; then",
          "     if cat /etc/system-release|grep -i 'Amazon Linux release 2023' ; then ",
          "       sudo yum -y install python3-pip",
          "       sudo pip3 install ansible",
          "       sudo yum -y install unzip",
          "     elif cat /etc/system-release|grep -i 'Amazon Linux release 2' ; then ",
          "       sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm",
          "       sudo yum install -y ansible",
          "       sudo yum -y install unzip",
          "     elif cat /etc/system-release|grep -i 'Amazon Linux AMI' ; then ",
          "       sudo pip install ansible --upgrade",
          "       sudo yum -y install unzip",
          "     elif cat /etc/system-release|grep -i 'Red Hat Enterprise Linux' ; then ",
          "       sudo yum -y install python3-pip",
          "       sudo pip3 install ansible",
          "       sudo yum -y install unzip",
          "     else",
          "       echo \"There was a problem installing or updating the required tools for the document. You can review the log files to help you correct the problem.\" >&2",
          "       exit 1",
          "     fi",
          "   elif cat /etc/issue|grep -i Ubuntu ; then ",
          "     UBUNTU_VERSION=$(cat /etc/issue | grep -i ubuntu | awk '{print $2}' |  awk -F'.' '{print $1}')",
          "     sudo apt-get update",
          "     if [ $(($UBUNTU_VERSION > 18)) == 1 ]; then",
          "       sudo DEBIAN_FRONTEND=noninteractive apt-get install python3-pip -y",
          "       sudo pip3 install ansible --upgrade",
          "     else",
          "       sudo DEBIAN_FRONTEND=noninteractive apt-get install python-pip -y",
          "       sudo pip install ansible --upgrade",
          "     fi",
          "     sudo DEBIAN_FRONTEND=noninteractive apt-get install unzip -y",
          "   else",
          "     echo \"There was a problem installing or updating the required tools for the document. You can review the log files to help you correct the problem.\" >&2",
          "     exit 1",
          "   fi",
          "fi",
          "echo \"Running Ansible in `pwd`\"",
          "#this section locates files and unzips them",
          "for zip in $(find -iname '*.zip'); do",
          "  unzip -o $zip",
          "done",
          "PlaybookFile=\"{{PlaybookFile}}\"",
          "if [ ! -f  \"${PlaybookFile}\" ] ; then",
          "   echo \"The specified Playbook file doesn't exist in the downloaded bundle. Please review the relative path and file name.\" >&2",
          "   exit 2",
          "fi",
          "if  [[ \"{{Check}}\" == True ]] ; then",
          "   ansible-playbook -i \"localhost,\" --check -c local -e \"{{ExtraVariables}}\" \"{{Verbose}}\" \"${PlaybookFile}\"",
          "else",
          "   ansible-playbook -i \"localhost,\" -c local -e \"{{ExtraVariables}}\" \"{{Verbose}}\" \"${PlaybookFile}\"",
          "fi"
        ]
      }
    }
  ]
}
```

追加修正した箇所はOS判定の部分で、先に`Amazon Linux 2`の判定に引っかからないよう、if文の先頭に`Amazon Linux release 2023`を追加し、else ifで`Amazon Linux 2`の判定とするようにしています。

また、`Amazon Linux 2023`に`Ansible`をインストールする方法としては、`Red Hat Enterprise Linux`の手順がそのまま利用できたので、 そのままコピペしています。

以下追加修正箇所抜粋。

```json:追加修正した条件判定部分抜粋
          "   if [ -f  \"/etc/system-release\" ] ; then",
          "     if cat /etc/system-release|grep -i 'Amazon Linux release 2023' ; then ",
          "       sudo yum -y install python3-pip",
          "       sudo pip3 install ansible",
          "       sudo yum -y install unzip",
          "     elif cat /etc/system-release|grep -i 'Amazon Linux release 2' ; then ",
          "       sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm",
          "       sudo yum install -y ansible",
          "       sudo yum -y install unzip",
```

上記を見て分かる通り`Amazon Linux 2`とは異なり`pip`によるインストールとなることから、`yum・dnf`による管理では無くなるためご注意ください。

また、今回は`Red Hat Enterprise Linux`の判定部分をそのままコピペしてきたので修正していませんが、気になる方は`yum`の部分を`dnf`に変更しても問題ありません。（`yum`も`dnf`にリンクされているので内部的には変わりませんが）

## カスタムドキュメントの実行
やることは通常の`Run Command`を実行する場合と変わらず、ドキュメント指定する際に先程作成したカスタムドキュメントを指定するだけです。

条件式以外はAWS公式のドキュメントと変えていないので、`AWS-ApplyAnsiblePlaybooks`と同じように指定すれば実行できます。

# おわりに
まだ登場して間もないため、対応していないサービスも多いですが、各サービスともに順次`Amazon Linux 2023`に対応していくと思うので、今回のような対応を行わなくてもすぐにAWS公式で対応してくれるかと思います。

`Amazon Linux 2023`でもいち早く`Ansible`のドキュメントを使いたいという方はAWS公式で対応するまでの暫定対処として参考にしてもらえればと思います。