AWS Greengrassのリソース構造をCLIを叩きながら読み解く

AWS Greengrassのリソース構造が少し複雑だったため、
CLIを叩きながらそのリソースの全体的な構造を把握し、それぞれの役割を整理します。

## 全体像

いきなりですが全体像です。

![gg_resources.png](https://qiita-image-store.s3.amazonaws.com/0/148726/f302fbbc-6914-4346-39e0-d768e11e03d9.png)


※ Coreのみで利用していたため、DeviceDefinitionについては割愛しています


## Coreと証明書の作成

まずはGreengrass Coreの実体となるAWS IoTのThingを作成します。
出力結果は後々必要になるので、変数に入れて取っておきます。

```shell
$ RESULT=$(aws iot create-thing --thing-name ${GROUP_NAME}_Core)
```

```shell:Result

$ echo $RESULT
{
    "thingName": "GreengrassTest_Core",
    "thingArn": "arn:aws:iot:ap-northeast-1:{aws_account_id}:thing/GreengrassTest_Core",
    "thingId": "{uuid v4 string}"
}
$ CORE_THING_ID=$(echo ${RESULT} | jq -r ".thingId")
$ CORE_THING_ARN=$(echo ${RESULT} | jq -r ".thingArn")
$ CORE_THING_NAME=$(echo ${RESULT} | jq -r ".thingName")
```

次に、作成したThingに証明書・ポリシーをアタッチします。

まずは証明書を作成。ARNは変数に保持しておき、ファイル名に使います。
jqを使いたい所なのですが、Keyの改行のせい？か、パース出来なかったためCLIのqueryを使いました。

```shell
$ CERT_ARN=$(aws iot create-keys-and-certificate --set-as-active \
--certificate-pem-outfile temp.cert.pem \
--public-key-outfile temp.public.key \
--private-key-outfile temp.private.key \
--query certificateArn --output text)
$ CERT_ID=$(echo $CERT_ARN | cut -d / -f 2)
$ mv temp.cert.pem ${CERT_ID:0:10}.cert.pem
$ mv temp.private.key ${CERT_ID:0:10}.private.key
$ mv temp.public.key ${CERT_ID:0:10}.public.key
```

証明書にポリシーをアタッチします。
ポリシーの作成は省略します。

```shell
$ aws iot attach-policy --policy-name Greengrass-pubsub-policy \
    --target ${CERT_ARN}
```

Thingに証明書をアタッチします。

```shell
$ aws iot attach-thing-principal \
    --thing-name ${CORE_THING_NAME} \
    --principal ${CERT_ARN}
```

仕上げに、Greengrass Coreを作成します。

```shell
$ aws greengrass create-core-definition \
  --cli-input-json '{
    "Name": "'"${GROUP_NAME}_Core_Definition"'",
    "InitialVersion": {
      "Cores": [{
          "Id": "'"$(uuidgen)"'",
          "ThingArn": "'"${CORE_THING_ARN}"'",
          "CertificateArn": "'"${CERT_ARN}"'"
      }]
    }
  }'
```

このコマンドによって、CoreDefinitionとCoreDefinitionVersionが1つずつ作成されます。
また、`Cores[].Id`のところでは`uuidgen`を使って生成したUUIDを入れています。
これはCoreを一意に識別するIDとなりますが、システム側で自動で生成される訳ではなく、自前で128bitのUUIDv4文字列を生成する必要があります。
また、この後のFunctionやSubscriptionでもこのUUIDが必要となります。

CoreDefinitionの内容は以下の様の形です。

```shell:CoreDefinition
{
    "Arn": "arn:aws:greengrass:ap-northeast-1:{AWS_ACCOUNT_ID}:/greengrass/definition/cores/{CORE_DEFINITION_ID}",
    "CreationTimestamp": "2018-08-18T12:01:21.810Z",
    "Id": "{CORE_DEFINITION_ID}",
    "LastUpdatedTimestamp": "2018-08-18T12:01:21.810Z",
    "LatestVersion": "{CORE_DEF_VER_ID}",
    "LatestVersionArn": "arn:aws:greengrass:ap-northeast-1:{AWS_ACCOUNT_ID}:/greengrass/definition/cores/{CORE_DEFINITION_ID}/versions/{CORE_DEF_VER_ID}",
    "Name": "GreengrassTest_Core_Definition"
}
```

CoreDefinitionVersionは以下のような形です。

```shell:CoreDefinitionVersion
{
    "Arn": "arn:aws:greengrass:ap-northeast-1:{AWS_ACCOUNT_ID}:/greengrass/definition/cores/{CORE_DEFINITION_ID}/versions/{CORE_DEF_VER_ID}",
    "CreationTimestamp": "2018-08-18T12:01:21.810Z",
    "Definition": {
        "Cores": [
            {
                "CertificateArn": "arn:aws:iot:ap-northeast-1:{AWS_ACCOUNT_ID}:cert/{CERT_ID}",
                "Id": "{CORE_ID}",
                "SyncShadow": false,
                "ThingArn": "arn:aws:iot:ap-northeast-1:{AWS_ACCOUNT_ID}:thing/GreengrassTest_Core"
            }
        ]
    },
    "Id": "{CORE_DEFINITION_ID}",
    "Version": "{CORE_DEF_VER_ID}"
}
```

CoreDefinitionで保持しているのはほぼメタデータのみであり、
実際の機能的？な部分は、CoreDefinitionVersionに記載されています。

また、`Cores`の部分は配列になっていますが、1つしか指定出来ません。

## Functionの作成

次に、LambdaFunctionをGreengrassに紐づけるFunctionDefinitionを作成します。
ここでは一旦空の（何れのLambdaFunctionも紐付けない）状態で空の定義を作成します。

```shell
$ aws greengrass create-function-definition \
  --cli-input-json '{
    "Name": "'"${GROUP_NAME}_Function_Definition"'",
    "InitialVersion": {
      "Functions": []
    }
  }'
```

リファレンス
https://docs.aws.amazon.com/cli/latest/reference/greengrass/create-function-definition.html

これで、CoreDefinitionと同様にFunctionDefinitionとFunctionDefinitionVersionが作成されます。

## Subscription、Resource、Loggerの作成

Functionと同様の手順で、Subscription、Resource、LoggerのDefinitionを作成していきます。

まずはSubscription、MQTTトピックのルーティングのようなことが行なえます。
これも空で作成。
リファレンスはこちら。
https://docs.aws.amazon.com/cli/latest/reference/greengrass/create-function-definition.html

```shell
$ aws greengrass create-subscription-definition \
  --cli-input-json '{
    "Name": "'"${GROUP_NAME}_Subscription_Definition"'",
    "InitialVersion": {
      "Subscriptions": []
    }
  }'
```

次にResource。
ローカルのファイルシステムやデバイスにアクセスする場合、ここに設定を記述します。
リファレンスはこちら。
https://docs.aws.amazon.com/cli/latest/reference/greengrass/create-resource-definition.html

```shell
$ aws greengrass create-resource-definition \
  --cli-input-json '{
    "Name": "'"${GROUP_NAME}_Resource_Definition"'",
    "InitialVersion": {
      "Resources": []
    }
  }'
```

そしてLogger。
Cloudwatchへログを流したり、ファイルシステムにログを保存する設定となります。
リファレンスはこちら。
https://docs.aws.amazon.com/cli/latest/reference/greengrass/create-logger-definition.html

```shell
$ CLI_INPUT_JSON='{
  "Name": "'"${GROUP_NAME}_Logger_Definition"'",
  "InitialVersion": {
    "Loggers": [
      {
        "Component": "Lambda",
        "Id": "'"$(uuidgen | awk '{print tolower($1)}')"'",
        "Level": "INFO",
        "Space": 25600,
        "Type": "FileSystem"
      },
      {
        "Component": "GreengrassSystem",
        "Id": "'"$(uuidgen | awk '{print tolower($1)}')"'",
        "Level": "INFO",
        "Space": 25600,
        "Type": "FileSystem"
      },
      {
        "Component": "Lambda",
        "Id": "'"$(uuidgen | awk '{print tolower($1)}')"'",
        "Level": "INFO",
        "Type": "AWSCloudWatch"
      },
      {
        "Component": "GreengrassSystem",
        "Id": "'"$(uuidgen | awk '{print tolower($1)}')"'",
        "Level": "INFO",
        "Type": "AWSCloudWatch"
      }
    ]
  }
}'

$ aws greengrass create-logger-definition \
  --cli-input-json $(echo $CLI_INPUT_JSON | jq -cr ".")
```

## Groupの作成

以上でGreengrass Groupに必要な各要素が揃ったので、
ついにGroupを作成していきます。

```shell
$ aws greengrass create-group --cli-input-json '{
    "Name": "'"${GROUP_NAME}"'",
    "InitialVersion": {
        "CoreDefinitionVersionArn": "'"${CORE_DEF_LATEST_VER_ARN}"'",
        "FunctionDefinitionVersionArn": "'"${FUNC_DEF_LATEST_VER_ARN}"'",
        "SubscriptionDefinitionVersionArn": "'"${SUBS_DEF_LATEST_VER_ARN}"'",
        "ResourceDefinitionVersionArn": "'"${RES_DEF_LATEST_VER_ARN}"'",
        "LoggerDefinitionVersionArn": "'"${LOG_DEF_LATEST_VER_ARN}"'"
    }
}'
```

これで新しいGreengrass Groupが出来たことが、Webからも確認出来るかと思います。
ですが、FunctionDefinitionなどはまだ空です。

## FunctionDefinitionの更新

FunctionDefinitionの更新の手順は、

1. 新しいFunctionDefinitionVersionの作成
2. 作成したFunctionDefinitionVersionのARNを指定し、新しいGroupVersionを作成


まずはFunctionDefinitionVersionの基となるjsonファイルを作成。
`FUNC_ID`はUUIDv4で、新しいFunctionを追加するときは新規に生成します。
`FUNC_DEF_ID`は、最初のGroup作成時に作成したものです。

```json:new_func_def_ver.json
{
  "FunctionDefinitionId": "${FUNC_DEF_ID}",
  "Functions": [
    {
      "FunctionArn": "arn:aws:lambda:::function:GGIPDetector:1",
      "FunctionConfiguration": {
        "Environment": {},
        "MemorySize": 32768,
        "Pinned": true,
        "Timeout": 3
      },
      "Id": "$(uuidgen)"
    },
    {
      "FunctionArn": "arn:aws:lambda:ap-northeast-1:{AWS_ACCOUNT_ID}:function:greengrass-hello-world:LATEST",
      "FunctionConfiguration": {
        "Environment": {},
        "MemorySize": 32768,
        "Pinned": true,
        "Timeout": 30
      },
      "Id": "$(uuidgen)"
    }

  ]
}
```

これを使って新しいFunctionDefinitionVersionを作成します。

```shell
$ aws greengrass create-function-definition-version --cli-input-json file://new_func_def_ver.json
```

```shell:Result
{
    "Arn": "arn:aws:greengrass:ap-northeast-1:{AWS_ACCOUNT_ID}:/greengrass/definition/functions/{FUNC_DEF_ID}/versions/{FUNC_DEF_VER_ID}",
    "CreationTimestamp": "2018-08-19T01:32:24.886Z",
    "Id": "{FUNC_DEF_ID}",
    "Version": "{FUNC_DEF_VER_ID}"
}
```

次にGroupVersionを更新します。
ここまでやって初めてGroupに変更が反映されます。

まずは、現在のGroupVersionを取得し・・・

```shell
$ LATEST_VERSION_ID=$(aws greengrass get-group --group-id $GROUP_ID | jq -r ".LatestVersion")
$ aws greengrass get-group-version --group-id $GROUP_ID --group-version-id $LATEST_VERSION_ID
```

指定した部分だけアップデート、という挙動ではないため、
上記手順で取得したjsonのFunctionDefinitionVersionなどを適宜書き換え・・・

```json:new_group_ver.json
{
  "FunctionDefinitionVersionArn": "arn:aws:greengrass:ap-northeast-1:{AWS_ACCOUNT_ID}:/greengrass/definition/functions/{FUNC_DEF_ID}/versions/{NEW_FUNC_DEF_VER_ID}",
  "CoreDefinitionVersionArn": "...",
  "LoggerDefinitionVersionArn": "...",
  "ResourceDefinitionVersionArn": "...",
  "SubscriptionDefinitionVersionArn": "...",
  "GroupId": "{GROUP_ID}"
}
```

最後に、新しいGroupVersionを作成します。
GroupVersionは自動的に最新のものが使用されます。

```shell
aws greengrass create-group-version --cli-input-json file://new_group_ver.json
```


これでGroupにLambdaFunctionが追加されました。

SubscriptionやResourceに関しては割愛しますが、
同様の手順でGroupを更新することが出来ます。

とっつきにくいリソース構造はしていましたが、
CLIでいじってみると結構わかりやすい構造でした。