Amazon ECSのタスク定義で改行やタブを含むファイルを出力する方法

数時間ほどハマってようやくたどり着いたのでメモ。


## やりたいこと

Amazon ECSのタスクでコンテナ実行時に簡単なスクリプトファイルを作成して実行したかったのです。

```python:hoge.py
print('hoge')
if False:
  print('no print')
```

みたいなPythonのファイルを作成して、`python hoge.py` するっていう。
インデントが効くか確認するために`if False:` とかしてます。


## `docker run` コマンドだと

お手元の端末で`docker` コマンドが利用できる前提で。


### `cat` コマンドでファイル作成

`cat` コマンドでヒアドキュメントを利用すると簡単にできます。
ヒアドキュメントについては下記が参考になります。

知ると便利なヒアドキュメント - Qiita
https://qiita.com/kite_999/items/e77fb521fc39454244e7


```console
> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "cat <<EOF > hoge.py
# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')
EOF
cat hoge.py;python hoge.py;"

# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')
hoge
```

なんとなく`docker run` コマンド側のインデントに合わせたくなりますが、そうするとヒアドキュメントのルール「`EOF` が単独で現れると終了」から外れてしまうので、後続のコマンドが巻き込まれてしまいます。

```console
> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "cat <<EOF > hoge.py
  # hogeって出力
  print('hoge')
  if False:
    # インデントが効くか
    print('no print')
  EOF
  cat hoge.py;python hoge.py;"

# 出力なし
```

`cat <<-EOF > hoge.py` と`<<` を`<<-` に変更して行頭のタブを無視してみましたが、`sh -c` で実行しているのが原因なのかダメでした。またエディタを利用しているとタブ→スペース変換されたりするのでムダにハマりそうです。

Pythonの実装でもムダにインデントがあると怒られるので諦めます。

```console
> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "cat <<EOF > hoge.py
  # hogeって出力
  print('hoge')
  if False:
    # インデントが効くか
    print('no print')
EOF
  cat hoge.py;python hoge.py;"


  # hogeって出力
  print('hoge')
  if False:
    # インデントが効くか
    print('no print')
  File "hoge.py", line 2
    print('hoge')
    ^
IndentationError: unexpected indent
```


### `echo` コマンドでファイル作成

`echo` コマンドでも複数行を改行付きで出力できるので、`cat` コマンドと同じことが実現できました。

Dockerfileで複数行を改行付きでechoする際にハマったので共有しておきます | ゲンゾウ用ポストイット
https://genzouw.com/entry/2019/04/17/080500


`-c` のあと、コンテナ内で実行するコマンドをダブルクォーテーション(`"` )で括り、`echo` コマンドで出力する文字列をエスケープ記号を付与したダブルクォーテーション(`\"` ) にして、Python実装ではシングルクォーテーション(`'` )を利用すると~~説明は長ったらしいですが、多分~~スッキリします。
こちらもインデントに気をつける必要がありました。

```console
> docker run -it --rm --entrypoint="sh" \
  python:3.7 \
  -c "echo \"
# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')
\" > hoge.py;cat hoge.py;python hoge.py;"

# hogeって出力
print('hoge')
if False:
  # インデントが効くか
  print('no print')

hoge
```

## Amazon ECSタスク定義だと

本題ですが、下記のように定義してやると実現できました。

### マネジメントコンソールとかで

- イメージ: python:3.7
- エンドポイント: ["sh", "-c"]
- コマンド: ["echo "# hogeって出力\nprint('hoge')\nif False:\n\t# インデントが効くか\n\tprint('no print')" > hoge.py;cat hoge.py;python hoge.py;"]


1行で定義しないとだめなので、改行を`\n` 、インデントのタブを`\t` に置き換える必要があります。(インデントは半角スペースでもOK)
試しに改行やタブをそのままでマネジメントコンソールのテキストボックスにコピペってみましたがダメでした(´・ω・｀)

`cat` コマンドでヒアドキュメントを利用する方法も試してみましたが、どうにも思った結果が得られず断念しました。


### AWS CloudFormationだと

AWS CloudFormationだとYAMLで定義できるのでおすすめです。
`AWS::ECS::TaskDefinition` の詳細については下記を参考ください。

AWS::ECS::TaskDefinition - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html


YAML中で改行を含ませる方法は下記を参考にさせてもらいました。

YAMLで値の中に改行を含ませる - 日々此妄想
https://hana-da.hatenadiary.org/entry/20081222/1229930738


```yaml:テンプレート抜粋
Resources:
  (略)
  ECSTaskDefinitionSample:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: "ECSTaskDefinitionSample"
      Cpu: 256
      Memory: 512
      TaskRoleArn: !Ref ECSTaskRole
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: test
          Image: python:3.7
          EntryPoint:
            - sh
            - -c
          Command:
            - |-
              echo "
              # hogeって出力
              print('hoge')
              if False:
                # インデントが効くか
                print('no print')
              " > hoge.py;
              cat hoge.py;
              python hoge.py;
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: /ecs/test/
              awslogs-region: !Sub "${AWS::Region}"
              awslogs-stream-prefix: hoge
```

作成したAmazon ESCのタスク定義からタスク実行すると下記のようにログ出力されて思ったとおりに実行されたことがわかります。
`aws logs get-log-events` コマンドでいい感じにログを取得する方法は下記が参考になります。

Amazon CloudWatch LogsのログをAWS CLIでいい感じに取得する - Qiita
https://qiita.com/kai_kou/items/60bbe314b74b9eaf7126


```console
> aws logs get-log-events \
  --log-group-name <ロググループ名> \
  --log-stream-name <ストリーム名> \
  --query "events[].[message]" \
  --output text

# hogeって出力
print('hoge')
if False:
        # インデントが効くか
        print('no print')
hoge
```


## まとめ

せっかくのコンテナサービスなんだからDockerイメージ作れって話ではあるのですが、ほんとにちょっとしたファイルを作成したいときに使えるかなと思います。


## 参考

知ると便利なヒアドキュメント - Qiita
https://qiita.com/kite_999/items/e77fb521fc39454244e7

Dockerfileで複数行を改行付きでechoする際にハマったので共有しておきます | ゲンゾウ用ポストイット
https://genzouw.com/entry/2019/04/17/080500

AWS::ECS::TaskDefinition - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html

YAMLで値の中に改行を含ませる - 日々此妄想
https://hana-da.hatenadiary.org/entry/20081222/1229930738

Amazon CloudWatch LogsのログをAWS CLIでいい感じに取得する - Qiita
https://qiita.com/kai_kou/items/60bbe314b74b9eaf7126