A shell script to deploy Lambda functions as components of GreengrassV2 to edge devices.
Published: 2022-11-09 03:41:21

I wrote a shell script to deploy Lambda functions as components of GreengrassV2 to edge devices.

I write some programs for edge devices using Lambda functions (Node.js) + GreengrassV2.
Every time I do the test on the edge device, I have to have a lot of manual operations, deploying Lambda functions, creating Greengrass Components, and deploying them to the edge device… , so I wrote a shell script to automate these processes.

## Why I wrote a shell script
- I developed the lambda function using Serverless Framework v2.
- I created a Greengrass Component using the AWS Console.
- I have a lot of manual operations, I have to set lambda function's version, topic that I want attach to the function, environment variables and so on.

I know AWS CLI makes it easy to complete the above operations, so I wrote a shell script which contains AWS CLI commands.

How to set up by AWS Console
https://docs.aws.amazon.com/ja_jp/greengrass/v2/developerguide/import-lambda-function-console.html

How to set up by AWS CLI
https://docs.aws.amazon.com/ja_jp/greengrass/v2/developerguide/import-lambda-function-cli.html

## Operations Before/After

### Before
- Deploy the Lambda function by Serverless Framework v2
  - `sls deploy …`
- Open the AWS Console and move to IoT Core menu
- Create the Greengrass Component
  - Set the Lambda function's version
  - Update the Component's version
  - Set the topic that I want to attach to the Lambda function
  - Set the environment variables
- Create the Greengrass Deployment settings
  - Just click some times

### After
- Run the script
  - `sh ./scripts/deploy.sh`

## What I did
I wrote a shell script for the Lambda function's deployment based on the article which I wrote before.

https://zenn.dev/nkmrkz/articles/greengrass-deployment
(Japanese only)

Folder
```
scripts
|- template
| |- component_template.json
| |- deployment_template.json
|- deployment.sh
```

The sample lambda function is like that
- Lambda function can get a message from the MQTT topic and publish a message to the topic.
- it use some environment variables.

So I need set the following things to Greengrass component.
- Lambda function's version
- Component's version
- Topic that I want to attach to the Lambda function
  - it can have wildcards
- Environment variables

template/component.json
```json
{
  "lambdaFunction":{
    "lambdaArn": "LambdaFunctionのARN:$LAMBDA_LATEST_VERSION",
    "componentName": "{ComponentName}",
    "componentVersion": "$CUSTOM_COMPONENT_VERSION",
    "componentDependencies": {
      "aws.greengrass.LambdaLauncher": {
        "versionRequirement": ">=2.0.0 <3.0.0",
        "dependencyType": "HARD"
      },
      "aws.greengrass.TokenExchangeService": {
        "versionRequirement": ">=2.0.0 <3.0.0",
        "dependencyType": "HARD"
      },
      "aws.greengrass.LambdaRuntimes": {
        "versionRequirement": ">=2.0.0 <3.0.0",
        "dependencyType": "SOFT"
      }
    },
    "componentLambdaParameters": {
      "eventSources": [
        {
          "topic": "{Topic Name}",
          "type": "IOT_CORE"
        }
      ],
      "maxQueueSize": 1000,
      "maxInstancesCount": 100,
      "maxIdleTimeInSeconds": 60,
      "timeoutInSeconds": 3,
      "statusTimeoutInSeconds": 60,
      "pinned": true,
      "inputPayloadEncodingType": "json",
      "environmentVariables": {
        "STAGE": "$STAGE_NAME",
        "ENV1": "$ENV1",
        "ENV2": "$ENV2"
      },
      "linuxProcessParams": {
        "isolationMode": "NoContainer",
        "containerParams": {}
      }
    }
  }
}
```

templates/deployment.json
```json
{
  "targetArn": "arn:aws:iot:ap-northeast-1:$ACCOUNT_ID:thinggroup/{TargetGroupForDeployment}",
  "deploymentName": "Deployment Setting Name",
  "components": {
      "aws.greengrass.Cli": {
          "componentVersion": "2.7.0"
      },
      "aws.greengrass.LegacySubscriptionRouter": {
          "componentVersion": "2.1.6",
          "configurationUpdate": {
              "merge": "{\"subscriptions\":{\"{SettingName}\":{\"id\":\"{Identifier}\",\"source\":\"component:{LambdaFunctionName}\",\"subject\":\"#\",\"target\":\"cloud\"}}}"
          },
          "runWith": {}
      },
      "aws.greengrass.Nucleus": {
          "componentVersion": "2.7.0",
          "runWith": {}
      },
      "{ComponentName}": {
          "componentVersion": "$CUSTOM_COMPONENT_VERSION",
          "runWith": {}
      }
  },
  "deploymentPolicies": {
      "failureHandlingPolicy": "DO_NOTHING",
      "componentUpdatePolicy": {
          "timeoutInSeconds": 60,
          "action": "NOTIFY_COMPONENTS"
      },
      "configurationValidationPolicy": {
          "timeoutInSeconds": 60
      }
  },
  "iotJobConfiguration": {}
}
```

deploy.sh
```sh
#!/bin/bash
echo "StageName(dev/stg) : "
read stage_name

echo "start lambda function deployment"

./node_modules/.bin/sls deploy --stage $stage_name --aws-profile {AWS CLI Profile Name}

env1=`grep "ENV1=" ./scripts/.secrets | awk -F "=" '{print$2}'`
env2=`grep "ENV2=" ./scripts/.secrets | awk -F "=" '{print$2}'`
account_id=`aws sts get-caller-identity --profile {AWS CLI Profile Name} --query "Account" |sed -e 's/"//g'`

# component version: $LambdaLtestVersion.0.0
lambda_latest_version=`aws lambda  list-versions-by-function --function-name {Function Name} --query 'Versions[-1].Version' --profile {AWS CLI Profile Name}| sed -e 's/"//g'`
custom_component_version=$lambda_latest_version.0.0

echo "create component.json"
cat ./scripts/templates/component_template.json | sed -e "s/\$ENV1/$env1/g"| sed -e "s/\$ENV2/$env2/g"| sed -e "s/\$CUSTOM_COMPONENT_VERSION/$custom_component_version/g" | sed -e "s/\$LAMBDA_LATEST_VERSION/$lambda_latest_version/g" | sed -e "s/\$STAGE_NAME/$stage_name/g" | sed -e "s/\$ACCOUNT_ID/$account_id/g" > ./scripts/component.json

echo "create deployment.json"
cat ./scripts/templates/deployment_template.json | sed -e "s/\$CUSTOM_COMPONENT_VERSION/$custom_component_version/g" | sed -e "s/\$STAGE_NAME/$stage_name/g" | sed -e "s/\$ACCOUNT_ID/$account_id/g" > ./scripts/deployment.json

echo "start create greengrass component"
aws greengrassv2 create-component-version --cli-input-json fileb://scripts/component.json --profile {AWS CLI Profile Name}

echo "start create greengrass deployment"
aws greengrassv2 create-deployment --cli-input-json fileb://scripts/deployment.json --profile {AWS CLI Profile Name}
```

- The script replace the value which is needed to deploy When it runs.
- I put some values on .secret
- I put some private values on .secret
  - `grep "ENV2=" ./scripts/.secrets | awk -F "=" '{print$2}'`
- By using AWS CLI, The script can get the AWS account ID and latest lambda version deployed in this script.
  - `aws sts get-caller-identity - profile {AWS CLI Profile Name} - query "Account" |sed -e 's/"//g'`
  - `aws lambda list-versions-by-function - function-name {Function Name} - query 'Versions[-1].Version' - profile {AWS CLI Profile Name}| sed -e 's/"//g'`
- The script dose not have any error handling. I will add them later, maybe.

.secrets
```
ENV1=xxxxx
ENV2=yyyyy
```

.gitignore（抜粋）
```
.secrets
component.json
deployment.json
```

That's all. Keep Playing!