3095 lines
116 KiB
Bash
3095 lines
116 KiB
Bash
#!/bin/sh
|
|
#
|
|
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
#
|
|
# GreengrassDeviceSetup helps to simplify the getting started process with AWS IoT Greengrass Core.
|
|
#
|
|
# This is the wrapper shell script part, the entry point for GreengrassDeviceSetup.
|
|
# This shell script executes a series of commands to check if the device environment is ready to run the core Python
|
|
# logic. Specifically, it will:
|
|
# 1. check and try install/bootstrap Python 3.7 if it is not available via the identified package management tool.
|
|
# 2. fallback to use Python 2 if Python 3.7 is not available and cannot be installed.
|
|
# 3. install the following Python dependencies required by core Python logic:
|
|
# 1. boto3
|
|
# 2. distro
|
|
# 3. configparser
|
|
# 4. retrying
|
|
# 5. yaspin
|
|
# 4. kick off the core Python logic
|
|
#
|
|
# The wrapper shell script has the following assumptions:
|
|
#
|
|
# Commands assume to be present on the device:
|
|
# 1. echo
|
|
# 2. exit
|
|
# 3. export
|
|
# 4. type
|
|
# 5. rm
|
|
#
|
|
# The script promptly checks the existence of the following commands:
|
|
# 1. id
|
|
# 2. cat
|
|
# 3. cd
|
|
# 4. date
|
|
# 5. mkdir
|
|
# 6. printf
|
|
# 7. sleep
|
|
# 8. kill
|
|
# 9. trap
|
|
# 10. seq
|
|
# 11. find
|
|
# Commands below are checked in core Python logic
|
|
# 11. chmod
|
|
# 12. sed
|
|
# 13. sysctl
|
|
# 14. grep
|
|
#
|
|
|
|
# Error codes
|
|
NO_ERR=0
|
|
ERR_NO_ROOT=200
|
|
ERR_NO_TMP_DIR=199
|
|
ERR_LOG_FILE=198
|
|
ERR_PREREQ=197
|
|
ERR_PKG_TOOL=196
|
|
ERR_UPDATE_PKG_LIST=195
|
|
ERR_PYTHON=194
|
|
ERR_WGET=193
|
|
ERR_GET_PIP_PY=192
|
|
ERR_BOTO3=191
|
|
ERR_DISTRO=190
|
|
ERR_CD=189
|
|
ERR_CONFIGPARSER=188
|
|
ERR_RETRYING=187
|
|
ERR_YASPIN=186
|
|
ERR_SETUPTOOLS=185
|
|
ERR_WHEEL=184
|
|
ERR_PARAM=183
|
|
|
|
# Constants
|
|
GG_DEVICE_SETUP_VERSION="1.0.0"
|
|
ECHO_HEADER="[GreengrassDeviceSetup]"
|
|
TMP_DIR="/tmp"
|
|
GET_PIP_PY="get-pip.py"
|
|
GET_PIP_PY_DOWNLOAD_DIR="$TMP_DIR"
|
|
GET_PIP_PY_URL="https://bootstrap.pypa.io/$GET_PIP_PY"
|
|
ID="id"
|
|
CAT="cat"
|
|
CD="cd"
|
|
DATE="date"
|
|
MKDIR="mkdir"
|
|
APT="apt"
|
|
APT_GET="apt-get"
|
|
YUM="yum"
|
|
OPKG="opkg"
|
|
PYTHON="python"
|
|
PYTHON27="${PYTHON}2.7"
|
|
PYTHON3="${PYTHON}3"
|
|
PYTHON37="${PYTHON}3.7"
|
|
PYTHON_USED="python"
|
|
PIP="pip"
|
|
RM="rm"
|
|
PIP_INSTALL_PATH="$TMP_DIR/greengrass-device-setup-bootstrap-tmp"
|
|
PIP_IMPORT_PATH="$TMP_DIR/greengrass-device-setup-bootstrap-tmp/lib/python3.7/site-packages"
|
|
GG_DEVICE_SETUP_SHELL_LOG_PATH="$TMP_DIR"
|
|
WGET="wget"
|
|
BOTO3="boto3"
|
|
DISTRO="distro"
|
|
CONFIGPARSER="ConfigParser"
|
|
RETRYING="retrying"
|
|
SPIN_PID="spin_pid"
|
|
SETUPTOOLS="setuptools"
|
|
WHEEL="wheel"
|
|
YASPIN="yaspin"
|
|
|
|
# Params
|
|
PKG_TOOL="@missing@"
|
|
PKG_LIST_UPDATED=1 # init to non-zero, which means package list is never updated
|
|
MY_PWD="@missing@"
|
|
GG_DEVICE_SETUP_SHELL_LOG_FILE="@missing@"
|
|
LOG_MSG="@missing@"
|
|
CMD_EXIT_CODE=1 # init to non-zero to prevent blind passes
|
|
SLEEP_TIME=0.1
|
|
|
|
# Python code
|
|
CODE=$($CAT <<EOF
|
|
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
|
|
# GreengrassDeviceSetup helps to simplify the getting started process with AWS IoT Greengrass Core.
|
|
#
|
|
# This is the single Python script to master the core logic to bootstrap Greengrass Core on the target device.
|
|
# It is invoked by GreengrassDeviceSetup wrapper shell script.
|
|
#
|
|
# It has the following assumptions/limits:
|
|
# 1. Python should be fully bootstrapped on the target device and have installed all of GreengrassDeviceSetup Python
|
|
# script required libraries.
|
|
# GreengrassDeviceSetup Python script will NOT attempt to install any of its required dependencies. Python
|
|
# bootstrapping should be handled by GreengrassDeviceSetup wrapper shell script or explicitly by the user.
|
|
# 2. GreengrassDeviceSetup requires sudo permission to run.
|
|
# 3. Basic linux commands and tools should be available on the target device. GreengrassDeviceSetup Python script will
|
|
# NOT attempt to install any of these basic linux commands from package management tool. A full list could be found
|
|
# in SUPPORTED_PKG_MANAGEMENT_TOOLS and REQUIRED_LINUX_CMD.
|
|
# 4. GreengrassDeviceSetup Python script only bootstraps a device from scratch. It will not pick up resources that
|
|
# exist on the device or pre-created in the cloud.
|
|
# 5. GreengrassDeviceSetup Python script will only pull down the latest version of GGC software.
|
|
#
|
|
# It performs the following steps:
|
|
# 1. Environment Pre-validation: check if all the required basic linux commands/tools are available on the device.
|
|
# 2. Greengrass Environment Bootstrap: install and configure environment to run GGC software.
|
|
# 3. Greengrass Cloud Bootstrap: create and configure required resources in AWS IoT Greengrass cloud for this GGC.
|
|
# 4. Greengrass Core Kick-off: start GGC on the device.
|
|
# 5. PostBootstrap: execute extra tasks remained after the above bootstrap, e.g., triggering and waiting for a
|
|
# deployment.
|
|
|
|
|
|
import argparse
|
|
import copy
|
|
import datetime
|
|
import grp
|
|
import json
|
|
import logging
|
|
import os
|
|
import platform
|
|
import pwd
|
|
import random
|
|
import re
|
|
import string
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
import uuid
|
|
from zipfile import ZipFile
|
|
|
|
import boto3
|
|
import distro
|
|
from botocore.exceptions import ClientError
|
|
from retrying import retry
|
|
from yaspin import yaspin
|
|
|
|
if sys.version_info.major < 3:
|
|
import ConfigParser
|
|
from urllib import urlretrieve
|
|
|
|
INPUT = raw_input
|
|
# HelloWorld Lambda function uses features of python 3 and will break if run on python 2,
|
|
# since Python2.7 is approaching its end-of-life.
|
|
|
|
else:
|
|
import configparser
|
|
from urllib.request import urlretrieve
|
|
|
|
INPUT = input # Py3.x no longer has raw_input
|
|
|
|
NAME_PREFIX = "GreengrassDeviceSetup_"
|
|
NUM_REGEX = "([0-9]+)"
|
|
INT_REGEX = NUM_REGEX + "$"
|
|
AWS_REGION_REGEX = "([a-z]{2}(-[a-z]+)+-[0-9]{1})"
|
|
GGC_VERSION_REGEX = "^" + NUM_REGEX + "\." + NUM_REGEX + "\." + NUM_REGEX + "$" # X.Y.Z
|
|
STDOUT_FORMAT = "[GreengrassDeviceSetup] %(message)s"
|
|
LOGGING_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
IAM = "iam"
|
|
IOT = "iot"
|
|
LAMBDA = "lambda"
|
|
GREENGRASS = "greengrass"
|
|
|
|
DEFAULT_AWS_REGION = "us-west-2"
|
|
DEFAULT_GROUP_NAME = NAME_PREFIX + "Group_" + str(uuid.uuid4())
|
|
DEFAULT_CORE_NAME = NAME_PREFIX + "Core_" + str(uuid.uuid4())
|
|
DEFAULT_GGC_ROOT_PATH = "/"
|
|
|
|
GGC_DEPENDENCY_CHECKER_FORMAT = "greengrass-dependency-checker-GGCv{}"
|
|
GGC_DEPENDENCY_CHECKER_ZIP_FORMAT = GGC_DEPENDENCY_CHECKER_FORMAT + ".zip"
|
|
GGC_DEPENDENCY_CHECKER_REMOTE_LOCATION_FORMAT = "https://github.com/aws-samples/aws-greengrass-samples/raw/master/" + \
|
|
GGC_DEPENDENCY_CHECKER_ZIP_FORMAT
|
|
LATEST_GG_PYTHON_SDK_REMOTE_LOCATION = "https://github.com/aws/aws-greengrass-core-sdk-python/archive/master.zip"
|
|
LATEST_GG_HELLO_WORLD_LAMBDA_REMOTE_LOCATION = "https://raw.githubusercontent.com/aws-samples/aws-greengrass-samples/" \
|
|
"master/hello-world-python/greengrassHelloWorld.py"
|
|
CGROUPFS_MOUNT_SCRIPT_REMOTE_LOCATION = "https://raw.githubusercontent.com/tianon/cgroupfs-mount/" \
|
|
"951c38ee8d802330454bdede20d85ec1c0f8d312/cgroupfs-mount"
|
|
GGC_SOFTWARE_REMOTE_LOCATION_FORMAT = "https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/" \
|
|
"{0}/greengrass-{1}-{2}-{0}.tar.gz"
|
|
ATS_ROOT_CA_RSA_2048_REMOTE_LOATION = "https://www.amazontrust.com/repository/AmazonRootCA1.pem"
|
|
|
|
LATEST_GGC_VERSION_AWARE = "1.10.0" # TODO : Automatically check the latest version
|
|
# TODO: The following should be updated per GreengrassDeviceSetup release through its release configuration
|
|
# https://issues.amazon.com/issues/GG-24677
|
|
|
|
DEFAULT_LOG_PATH = "./"
|
|
DEFAULT_DEPLOYMENT_TIMEOUT = 3 * 60 # 3 minutes
|
|
DEFAULT_GGC_MQTT_KEEP_ALIVE = 600 # 600 seconds
|
|
CONFIG_DEPLOYMENT_TIMEOUT = DEFAULT_DEPLOYMENT_TIMEOUT
|
|
DEFAULT_HAS_HELLO_WORLD_LAMBDA = False
|
|
|
|
CMD_BOOTSTRAP_GG = "bootstrap-greengrass"
|
|
CMD_BOOTSTRAP_GG_INTERACTIVE = CMD_BOOTSTRAP_GG + "-interactive"
|
|
|
|
KEY_SUB_CMD = "subcommand"
|
|
KEY_HAS_HELLO_WORLD_LAMBDA = "HasHelloWorldLambda"
|
|
KEY_AWS_ACCESS_KEY_ID = "AwsAccessKeyId"
|
|
KEY_AWS_SECRET_ACCESS_KEY = "AwsSecretAccessKey"
|
|
KEY_AWS_SESSION_TOKEN = "AwsSessionToken"
|
|
KEY_AWS_REGION = "Region"
|
|
KEY_GROUP_NAME = "GroupName"
|
|
KEY_CORE_NAME = "CoreName"
|
|
KEY_GGC_ROOT_PATH = "GGCRootPath"
|
|
KEY_GGC_VERSION = "GGCVersion"
|
|
KEY_DEPLOYMENT_TIMEOUT = "DeploymentTimeout"
|
|
KEY_LOG_PATH = "LogPath"
|
|
KEY_LOG_FILE = "LogFile"
|
|
|
|
KEY_LOGGER = "Logger"
|
|
KEY_DEVICE_PLATFORM = "DevicePlatform"
|
|
KEY_ARCHITECTURE = "Architecture"
|
|
KEY_DISTRIBUTION = "Distribution"
|
|
KEY_PKG_MANAGEMENT_TOOL = "PackageManagementTool"
|
|
KEY_ADD_USER_TOOL = "AddUserTool"
|
|
KEY_ADD_GROUP_TOOL = "AddGroupTool"
|
|
KEY_NEED_REBOOT = "NeedReboot"
|
|
KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN = "RecoverFromReboot"
|
|
KEY_BOTO_SESSION = "BotoSession"
|
|
KEY_GG_ACCOUNT_SERVICE_ROLE = "GGServiceRole"
|
|
KEY_LAMBDA_EXECUTION_ROLE = "LambdaExecutionRole"
|
|
KEY_CORE_THING_ARN = "CoreThingArn"
|
|
KEY_CORE_CERT_ARN = "CoreCertArn"
|
|
KEY_CORE_CERT_ID = "CoreCertId"
|
|
KEY_CORE_CERT_PEM = "CoreCertPem"
|
|
KEY_CORE_CERT_FILE_LOCATION = "CoreCertFilePath"
|
|
KEY_CORE_PRIV_KEY = "CorePrivKey"
|
|
KEY_CORE_PRIV_KEY_FILE_LOCATION = "CorePrivKeyFilePath"
|
|
KEY_ROOT_CA_FILE_LOCATION = "RootCAFilePath"
|
|
KEY_CORE_DEF_VER_ARN = "CoreDefinitionVersionArn"
|
|
KEY_FUNCTION_DEF_VER_ARN = "FunctionDefinitionVersionArn"
|
|
KEY_SUBSCRIPTION_VER_ARN = "SubscriptionVersionArn"
|
|
KEY_LOGGER_VER_ARN = "LoggerVersionArn"
|
|
KEY_GROUP_ID = "GroupId"
|
|
KEY_GROUP_DEF_VER_ARN = "GroupVersionArn"
|
|
KEY_GROUP_DEF_VER_ID = 'GroupVersionId'
|
|
KEY_DEPLOYMENT_ARN = 'DeploymentArn'
|
|
KEY_DEPLOYMENT_ID = 'DeploymentId'
|
|
KEY_IOT_DATA_ENDPOINT = "IotDataEndpoint"
|
|
KEY_LOGGER_NAME = "LoggerName"
|
|
KEY_USE_SYSTEMD = "UseSystemd"
|
|
KEY_HELLO_WORLD_LAMBDA_VERSIONED_ARN = "HelloWorldLambdaVersionedArn"
|
|
KEY_IS_CONFIG_FILE_EXIST = "IsConfigFileExist"
|
|
KEY_CONFIG_INFO_FILE_PATH = ""
|
|
KEY_CONTINUE_WITH_OLD_CONFIG_OR_NOT = "ContinueRebootOrNot"
|
|
KEY_RESPONSE_METADATA = "ResponseMetadata"
|
|
KEY_HTTP_STATUS_CODE = "HTTPStatusCode"
|
|
KEY_VERBOSE = "verbose"
|
|
|
|
REQUIRED_LINUX_CMD = {
|
|
"chmod", "sysctl", "grep", "sed"
|
|
}
|
|
PLATFORM_X86_64 = "x86_64"
|
|
PLATFORM_ARMV7L = "armv7l"
|
|
PLATFORM_AARCH64 = "aarch64"
|
|
SUPPORTED_PLATFORMS = {
|
|
PLATFORM_X86_64,
|
|
PLATFORM_ARMV7L,
|
|
PLATFORM_AARCH64,
|
|
}
|
|
|
|
PKG_MANAGEMENT_TOOL_APT = "apt"
|
|
PKG_MANAGEMENT_TOOL_APT_GET = "apt-get"
|
|
PKG_MANAGEMENT_TOOL_YUM = "yum"
|
|
PKG_MANAGEMENT_TOOL_OPKG = "opkg"
|
|
SUPPORTED_PKG_MANAGEMENT_TOOLS = {
|
|
PKG_MANAGEMENT_TOOL_APT,
|
|
PKG_MANAGEMENT_TOOL_APT_GET,
|
|
PKG_MANAGEMENT_TOOL_YUM,
|
|
PKG_MANAGEMENT_TOOL_OPKG,
|
|
}
|
|
|
|
UPDATE = "update"
|
|
USER_ADD = "useradd"
|
|
GROUP_ADD = "groupadd"
|
|
ADD_USER = "adduser"
|
|
ADD_GROUP = "addgroup"
|
|
# runbook that captures how to install all kinds of dependencies
|
|
OPKG_INSTALL_RUNBOOK = {
|
|
UPDATE: "opkg update",
|
|
USER_ADD: "opkg install shadow-useradd",
|
|
GROUP_ADD: "opkg install shadow-groupadd",
|
|
}
|
|
YUM_INSTALL_RUNBOOK = {
|
|
UPDATE: "yum -y update",
|
|
USER_ADD: "yum -y install shadow-utils",
|
|
GROUP_ADD: "yum -y install shadow-utils",
|
|
}
|
|
APT_GET_INSTALL_RUNBOOK = {
|
|
UPDATE: "apt-get -y update",
|
|
}
|
|
INSTALL_RUNBOOKS = {
|
|
PKG_MANAGEMENT_TOOL_APT: APT_GET_INSTALL_RUNBOOK, # apt-get is the lower level backend for apt
|
|
PKG_MANAGEMENT_TOOL_APT_GET: APT_GET_INSTALL_RUNBOOK,
|
|
PKG_MANAGEMENT_TOOL_YUM: YUM_INSTALL_RUNBOOK,
|
|
PKG_MANAGEMENT_TOOL_OPKG: OPKG_INSTALL_RUNBOOK,
|
|
}
|
|
|
|
AWS = "aws"
|
|
AWS_CN = "aws-cn"
|
|
AWS_US_GOV = "aws-us-gov"
|
|
PARTITION_OVERRIDES = {
|
|
"cn-north-1": AWS_CN,
|
|
"us-gov-west-1": AWS_US_GOV,
|
|
}
|
|
|
|
|
|
def main():
|
|
try:
|
|
GGCBootstrapper_1_x().install()
|
|
except StepError as e:
|
|
print(e)
|
|
except Exception as e:
|
|
print("Unexpected error: %s" %e)
|
|
|
|
# aiming at bootstrapping GGC v1.x
|
|
class GGCBootstrapper_1_x(object):
|
|
|
|
def __init__(self):
|
|
self._args = {}
|
|
# step sequence matters
|
|
# Steps for Installation
|
|
self._install_steps = [
|
|
ArgsCollection,
|
|
EnvironmentPreValidation,
|
|
GreengrassEnvironmentBootstrap,
|
|
CoreSoftwareBootstrap,
|
|
]
|
|
# Steps for Bootstrapping
|
|
self._boostrap_steps = [
|
|
ArgsCollection,
|
|
EnvironmentPreValidation,
|
|
GreengrassEnvironmentBootstrap,
|
|
GreengrassCloudBootstrap,
|
|
GreengrassCoreKickoff,
|
|
PostBootstrap,
|
|
]
|
|
|
|
def install(self):
|
|
for step in self._install_steps:
|
|
self._args = step(self._args).execute()
|
|
|
|
def bootstrap(self):
|
|
for step in self._boostrap_steps:
|
|
self._args = step(self._args).execute()
|
|
|
|
|
|
# bootstrap steps
|
|
class Step(object):
|
|
|
|
def __init__(self, args):
|
|
self._args = args
|
|
|
|
def execute(self):
|
|
return self._args
|
|
|
|
@staticmethod
|
|
def cmd_exist(cmd):
|
|
code, _, _ = Step.run_linux_cmd("type {} > /dev/null 2>&1".format(cmd))
|
|
return code == 0
|
|
|
|
@staticmethod
|
|
def run_linux_cmd_raise_on_failure(cmd_string, err_code, err_msg):
|
|
ret_code, std_out, std_err = Step.run_linux_cmd(cmd_string)
|
|
if ret_code != 0:
|
|
raise StepError(code=err_code, message=err_msg + " Stdout: {} Stderr: {}".format(std_out, std_err))
|
|
return ret_code, std_out, std_err
|
|
|
|
@staticmethod
|
|
def run_linux_cmd(cmd_string):
|
|
proc = subprocess.Popen(cmd_string, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
std_out, std_err = proc.communicate()
|
|
return proc.returncode, std_out, std_err
|
|
|
|
@staticmethod
|
|
def get_random_string(length):
|
|
return "".join(random.sample(string.ascii_letters + string.digits, length))
|
|
|
|
|
|
# args validators
|
|
def _validate_with_regex(val, regex):
|
|
return val is not None and re.compile(regex).match(val) is not None
|
|
|
|
|
|
def validate_yes_or_no(val):
|
|
return val == "yes" or val == "no"
|
|
|
|
|
|
def validate_string_not_none_non_empty(val):
|
|
return val is not None and len(val) > 0
|
|
|
|
|
|
def validate_aws_region(val):
|
|
return _validate_with_regex(val, AWS_REGION_REGEX)
|
|
|
|
|
|
def validate_ggc_version(val):
|
|
return _validate_with_regex(val, GGC_VERSION_REGEX)
|
|
|
|
|
|
def validate_int_string(val):
|
|
return _validate_with_regex(val, INT_REGEX)
|
|
|
|
|
|
class ArgsCollection(Step):
|
|
ARGS_SHOULD_ALWAYS_RETRY = {KEY_HAS_HELLO_WORLD_LAMBDA}
|
|
ARG_DEFAULTS = {
|
|
KEY_HAS_HELLO_WORLD_LAMBDA: DEFAULT_HAS_HELLO_WORLD_LAMBDA,
|
|
KEY_AWS_REGION: DEFAULT_AWS_REGION,
|
|
KEY_GROUP_NAME: DEFAULT_GROUP_NAME,
|
|
KEY_CORE_NAME: DEFAULT_CORE_NAME,
|
|
KEY_GGC_ROOT_PATH: DEFAULT_GGC_ROOT_PATH,
|
|
KEY_GGC_VERSION: LATEST_GGC_VERSION_AWARE,
|
|
KEY_DEPLOYMENT_TIMEOUT: DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
KEY_LOG_PATH: DEFAULT_LOG_PATH,
|
|
}
|
|
ARG_MESSAGES = {
|
|
KEY_HAS_HELLO_WORLD_LAMBDA: "Do you want to include a Hello World Lambda function and "
|
|
"deploy the Greengrass group? Enter 'yes' or 'no'.",
|
|
KEY_AWS_ACCESS_KEY_ID: "Enter your AWS access key ID, or press 'Enter' to read it from"
|
|
" your environment variables.",
|
|
KEY_AWS_SECRET_ACCESS_KEY: "Enter your AWS secret access key, or press 'Enter' to "
|
|
"read it from your environment variables.",
|
|
KEY_AWS_SESSION_TOKEN: "Enter your AWS session token, which is required only when you are "
|
|
"using temporary security credentials. Press 'Enter' to read it from "
|
|
"your environment variables or if the session token is not required.",
|
|
KEY_AWS_REGION: "Enter the AWS Region where you want to create a Greengrass group, "
|
|
"or press 'Enter' to use '{}'.".format(DEFAULT_AWS_REGION),
|
|
KEY_GROUP_NAME: "Enter a name for the Greengrass group, or press 'Enter' to use '{}'.".format(DEFAULT_GROUP_NAME),
|
|
KEY_CORE_NAME: "Enter a name for the Greengrass core, or press 'Enter' to use '{}'.".format(DEFAULT_CORE_NAME),
|
|
KEY_GGC_ROOT_PATH: "Enter the installation path for the Greengrass core software, "
|
|
"or press 'Enter' to use '{}'.".format(DEFAULT_GGC_ROOT_PATH),
|
|
KEY_GGC_VERSION: "Enter a number for the Greengrass core version,"
|
|
" or press 'Enter' to use latest version '{}'.".format(LATEST_GGC_VERSION_AWARE),
|
|
KEY_DEPLOYMENT_TIMEOUT: "Enter a deployment timeout (in seconds), "
|
|
"or press 'Enter' to use '{}'.".format(DEFAULT_DEPLOYMENT_TIMEOUT),
|
|
KEY_LOG_PATH: "Enter the path for the Greengrass environment setup log file, "
|
|
"or press 'Enter' to use '{}'. ".format(DEFAULT_LOG_PATH),
|
|
KEY_CONTINUE_WITH_OLD_CONFIG_OR_NOT: "Do you want to reuse the configuration from your previous session? "
|
|
"Enter 'yes' to reuse the configuration or 'no' to restart the installation.",
|
|
}
|
|
|
|
REQUIRED_CREDENTIALS_ARGS_IN_ORDER = [
|
|
KEY_AWS_ACCESS_KEY_ID,
|
|
KEY_AWS_SECRET_ACCESS_KEY,
|
|
KEY_AWS_SESSION_TOKEN,
|
|
]
|
|
|
|
REQUIRED_ARGS_IN_ORDER = [
|
|
KEY_AWS_REGION,
|
|
KEY_GROUP_NAME,
|
|
KEY_CORE_NAME,
|
|
KEY_GGC_ROOT_PATH,
|
|
KEY_GGC_VERSION,
|
|
KEY_HAS_HELLO_WORLD_LAMBDA,
|
|
KEY_DEPLOYMENT_TIMEOUT,
|
|
KEY_LOG_PATH,
|
|
]
|
|
|
|
ARG_VALIDATORS = {
|
|
KEY_HAS_HELLO_WORLD_LAMBDA: validate_yes_or_no,
|
|
KEY_CONTINUE_WITH_OLD_CONFIG_OR_NOT: validate_yes_or_no,
|
|
KEY_AWS_REGION: validate_aws_region,
|
|
KEY_GROUP_NAME: validate_string_not_none_non_empty,
|
|
KEY_CORE_NAME: validate_string_not_none_non_empty,
|
|
KEY_GGC_ROOT_PATH: validate_string_not_none_non_empty,
|
|
KEY_GGC_VERSION: validate_ggc_version,
|
|
KEY_DEPLOYMENT_TIMEOUT: validate_int_string,
|
|
KEY_LOG_PATH: validate_string_not_none_non_empty,
|
|
}
|
|
ARG_CONVERTERS = {
|
|
KEY_HAS_HELLO_WORLD_LAMBDA: lambda x: x == "yes" or x is True,
|
|
KEY_DEPLOYMENT_TIMEOUT: lambda x: int(x),
|
|
KEY_CONTINUE_WITH_OLD_CONFIG_OR_NOT: lambda x: x == "yes",
|
|
}
|
|
|
|
TMP_DIR = tempfile.gettempdir()
|
|
GG_DEVICE_SETUP_CONFIG_FILE = "GreengrassDeviceSetup.config.info"
|
|
|
|
def __init__(self, args=None):
|
|
super(ArgsCollection, self).__init__(args)
|
|
|
|
def execute(self):
|
|
self._args = ArgsCollection.collect_cmdline_args()
|
|
ArgsCollection.pre_validation()
|
|
ArgsCollection.check_config_info_file(self._args)
|
|
ArgsCollection.collect_or_generate_args(self._args)
|
|
ArgsCollection.prepare_logger(self._args)
|
|
ArgsCollection.log_config_info_source(self._args)
|
|
ArgsCollection.set_deploy_time_out(self._args)
|
|
ArgsCollection.validate_latest_ggc_version(self._args)
|
|
ArgsCollection.disable_helloworld_lambda_if_runtime_python27(self._args)
|
|
|
|
return self._args
|
|
|
|
# Check whether the conditions are suitable for GGC running. Stop immediately, if checking has failed
|
|
@staticmethod
|
|
def pre_validation():
|
|
if ArgsCollection.check_whether_ggc_is_running() is True:
|
|
print("GreengrassDeviceSetup has stopped because the Greengrass core software is already running on the device.")
|
|
exit(StepError.ERR_ENV_PREVALIDATE)
|
|
|
|
@staticmethod
|
|
def check_whether_ggc_is_running():
|
|
proc_str = "/proc"
|
|
pattern = re.compile('.*/greengrass/ggc/packages/[0-9]+\.[0-9]+\.[0-9]+/bin/daemon.*')
|
|
pids = [pid for pid in os.listdir(proc_str) if pid.isdigit()]
|
|
for pid in pids:
|
|
try:
|
|
process = open(os.path.join(proc_str, pid, 'cmdline'), 'rb').read().decode('utf-8')
|
|
if pattern.match(process):
|
|
return True
|
|
except IOError:
|
|
# proc has already terminated
|
|
continue
|
|
return False
|
|
|
|
@classmethod
|
|
def check_config_info_file(cls, args):
|
|
gg_device_setup_config_info_file = os.path.abspath(
|
|
os.path.join("./", ArgsCollection.GG_DEVICE_SETUP_CONFIG_FILE))
|
|
args[KEY_CONFIG_INFO_FILE_PATH] = gg_device_setup_config_info_file
|
|
|
|
args[KEY_IS_CONFIG_FILE_EXIST] = os.path.exists(args[KEY_CONFIG_INFO_FILE_PATH])
|
|
args[KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN] = False
|
|
if args[KEY_IS_CONFIG_FILE_EXIST]: # The device should has been rebooted
|
|
# if judgement is False, the previous config info would not be reused
|
|
if not ArgsCollection._collect_args_helper(args, KEY_CONTINUE_WITH_OLD_CONFIG_OR_NOT):
|
|
remove_file_or_dir(args[KEY_CONFIG_INFO_FILE_PATH])
|
|
else:
|
|
args[KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN] = True
|
|
# Read the customer's inputs from the file "GreengrassDeviceSetup.config.info"
|
|
ArgsCollection.read_config_info_file(args)
|
|
|
|
@classmethod
|
|
def read_config_info_file(cls, args):
|
|
gg_device_setup_config_info_file = args[KEY_CONFIG_INFO_FILE_PATH]
|
|
with open(gg_device_setup_config_info_file, 'r') as f:
|
|
inputs_before_reboot = json.load(f)
|
|
for key, value in inputs_before_reboot.items():
|
|
converter = ArgsCollection.ARG_CONVERTERS.get(key, lambda x: x)
|
|
stored_input = value
|
|
args[key] = converter(stored_input)
|
|
|
|
@classmethod
|
|
def collect_cmdline_args(cls, inputs=None):
|
|
arg_parser = argparse.ArgumentParser()
|
|
|
|
subparsers = arg_parser.add_subparsers(dest=KEY_SUB_CMD)
|
|
|
|
# TODO: Expand to have more sub-commands per new features
|
|
ArgsCollection._build_subcommand_bootstrap_gg(subparsers)
|
|
ArgsCollection._build_subcommand_bootstrap_gg_interactive(subparsers)
|
|
|
|
# make parsed args into dict and get a deep copy of it so as not to mess around with arg parsing results
|
|
return copy.deepcopy(vars(arg_parser.parse_args(args=inputs)))
|
|
|
|
@classmethod
|
|
def _build_subcommand_bootstrap_gg(cls, subparsers):
|
|
subparser = subparsers.add_parser(CMD_BOOTSTRAP_GG)
|
|
subparser.add_argument("--hello-world-lambda", action="store_true", required=False,
|
|
dest=KEY_HAS_HELLO_WORLD_LAMBDA,
|
|
default=DEFAULT_HAS_HELLO_WORLD_LAMBDA,
|
|
help="If specified, a Hello World Lambda function is included in the Greengrass group. "
|
|
"This function continuously publishes MQTT messages to the "
|
|
"'hello/world' topic through the Greengrass core.")
|
|
subparser.add_argument("--aws-access-key-id", action="store", required=False, dest=KEY_AWS_ACCESS_KEY_ID,
|
|
help="(string) The access key ID from the user's AWS account. This is required only to"
|
|
" enter the access key ID as an input value "
|
|
"(not from environment variables).")
|
|
subparser.add_argument("--aws-secret-access-key", action="store", required=False,
|
|
dest=KEY_AWS_SECRET_ACCESS_KEY,
|
|
help="(string) The secret access key from the user's AWS account. "
|
|
"This is required only to enter the secret access key as an input value "
|
|
"(not from environment variables).")
|
|
subparser.add_argument("--aws-session-token", action="store", required=False, dest=KEY_AWS_SESSION_TOKEN,
|
|
help="(string) [Optional] The session token from the user's AWS account. "
|
|
"This is required only when you are using temporary security credentials and "
|
|
"to enter session token as an input value (not from environment variables).")
|
|
subparser.add_argument("--region", action="store", required=False, dest=KEY_AWS_REGION,
|
|
default=DEFAULT_AWS_REGION,
|
|
help="(string) The AWS Region where the Greengrass group should be created. "
|
|
"Defaults to 'us-west-2'.")
|
|
subparser.add_argument("--group-name", action="store", required=False, dest=KEY_GROUP_NAME,
|
|
help="(string) The name of the Greengrass group. "
|
|
"Defaults to 'GreengrassDeviceSetup_Group_<guid>'.")
|
|
subparser.add_argument("--core-name", action="store", required=False, dest=KEY_CORE_NAME,
|
|
help="(string) The thing name of the Greengrass core. "
|
|
"Defaults to 'GreengrassDeviceSetup_Core_<guid>'.")
|
|
subparser.add_argument("--ggc-root-path", action="store", required=False, dest=KEY_GGC_ROOT_PATH,
|
|
default=DEFAULT_GGC_ROOT_PATH,
|
|
help="(string) The location where the Greengrass core software should be installed. "
|
|
"Defaults to '/'.")
|
|
subparser.add_argument("--ggc-version", action="store", required=False, dest=KEY_GGC_VERSION,
|
|
default=LATEST_GGC_VERSION_AWARE,
|
|
help="(string) The version of Greengrass core software that GreengrassDeviceSetup "
|
|
"should install. Defaults to the latest version."
|
|
"This option is currently not supported and is reserved for future use.")
|
|
subparser.add_argument("--deployment-timeout", action="store", type=int, required=False,
|
|
default=DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
dest=KEY_DEPLOYMENT_TIMEOUT,
|
|
help="(integer) The number of seconds before GreengrassDeviceSetup stops checking "
|
|
"the status of the Greengrass group deployment. This is used only when the "
|
|
"Greengrass group includes the Hello World Lambda function. "
|
|
"Otherwise, the group is not deployed. Defaults to '180'.")
|
|
subparser.add_argument("--log-path", action="store", required=False, dest=KEY_LOG_PATH,
|
|
default=DEFAULT_LOG_PATH,
|
|
help="(string) The location of the log file that contains information about "
|
|
"Greengrass environment setup operations. Defaults to './'.")
|
|
subparser.add_argument("--verbose", action="store_true", dest=KEY_VERBOSE, help="Makes GreengrassDeviceSetup"
|
|
" verbose during the operation. Useful for debugging and seeing what's going "
|
|
"on 'under the hood'.")
|
|
|
|
@classmethod
|
|
def _build_subcommand_bootstrap_gg_interactive(cls, subparsers):
|
|
subparser = subparsers.add_parser(CMD_BOOTSTRAP_GG_INTERACTIVE)
|
|
subparser.add_argument("--verbose", action="store_true", dest=KEY_VERBOSE, help="Makes GreengrassDeviceSetup"
|
|
" verbose during the operation. Useful for debugging and seeing what's going "
|
|
"on 'under the hood'.")
|
|
|
|
@classmethod
|
|
def collect_or_generate_args(cls, args):
|
|
if args.get(KEY_SUB_CMD) == CMD_BOOTSTRAP_GG_INTERACTIVE:
|
|
ArgsCollection._collect_args_from_input(args)
|
|
else:
|
|
# not validating credentials as we only install
|
|
# ArgsCollection.validate_credentials(args)
|
|
ArgsCollection._set_args_to_default_if_missing(args)
|
|
|
|
@classmethod
|
|
def _collect_args_from_input(cls, args):
|
|
for key in cls.REQUIRED_CREDENTIALS_ARGS_IN_ORDER:
|
|
if not args[KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN] or (key not in cls.ARG_VALIDATORS):
|
|
args[key] = ArgsCollection._collect_args_helper(args, key)
|
|
ArgsCollection.validate_credentials(args)
|
|
for key in cls.REQUIRED_ARGS_IN_ORDER:
|
|
if not args[KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN] or (key not in cls.ARG_VALIDATORS):
|
|
args[key] = ArgsCollection._collect_args_helper(args, key)
|
|
|
|
@classmethod
|
|
def _collect_args_helper(cls, args, key):
|
|
if key == KEY_HAS_HELLO_WORLD_LAMBDA and not (sys.version_info.major == 3 and sys.version_info.minor == 7):
|
|
return DEFAULT_HAS_HELLO_WORLD_LAMBDA
|
|
|
|
if key == KEY_GGC_VERSION:
|
|
return LATEST_GGC_VERSION_AWARE
|
|
|
|
msg = cls.ARG_MESSAGES[key]
|
|
converter = cls.ARG_CONVERTERS.get(key, lambda x: x)
|
|
should_retry = True
|
|
validate = cls.ARG_VALIDATORS.get(key, lambda x: True)
|
|
while should_retry:
|
|
print(msg)
|
|
input_val = INPUT()
|
|
if not validate(input_val):
|
|
|
|
if key in cls.ARGS_SHOULD_ALWAYS_RETRY: # for those that always need valid inputs from user
|
|
print("Invalid input.")
|
|
continue
|
|
|
|
default_val = cls.ARG_DEFAULTS.get(key, None)
|
|
if len(input_val) > 0 or default_val is None:
|
|
print("Invalid input.")
|
|
continue
|
|
input_val = default_val # received newline from stdin, use default
|
|
should_retry = False
|
|
return converter(input_val)
|
|
|
|
@classmethod
|
|
def _check_both_exist_access_id_and_secret_key(cls, args):
|
|
if not args[KEY_AWS_ACCESS_KEY_ID] or not args[KEY_AWS_SECRET_ACCESS_KEY]:
|
|
args[KEY_AWS_SESSION_TOKEN] = None
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def _check_key_in_env(cls, args):
|
|
args[KEY_AWS_ACCESS_KEY_ID] = os.environ.get('AWS_ACCESS_KEY_ID')
|
|
args[KEY_AWS_SECRET_ACCESS_KEY] = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
|
args[KEY_AWS_SESSION_TOKEN] = os.environ.get('AWS_SESSION_TOKEN')
|
|
return cls._check_both_exist_access_id_and_secret_key(args)
|
|
|
|
@classmethod
|
|
def validate_credentials(cls, args):
|
|
# Check custom config
|
|
if cls._check_both_exist_access_id_and_secret_key(args):
|
|
return
|
|
# Check environment variables
|
|
if cls._check_key_in_env(args):
|
|
return
|
|
err_msg = "The credentials were not acquired by GreengrassDeviceSetup."
|
|
raise StepError(code=StepError.ERR_INVALID_CREDENTIALS, message=err_msg)
|
|
|
|
@classmethod
|
|
def _set_args_to_default_if_missing(cls, args):
|
|
for key in cls.ARG_VALIDATORS.keys():
|
|
default_val = cls.ARG_DEFAULTS.get(key)
|
|
if args.get(key) is None and default_val is not None:
|
|
args[key] = default_val
|
|
|
|
@classmethod
|
|
def prepare_logger(cls, args):
|
|
verbose_name = "VERBOSE"
|
|
verbose_logging_level = 15
|
|
logging.addLevelName(verbose_logging_level, verbose_name)
|
|
|
|
def verbose(self, message, *args, **kws):
|
|
if self.isEnabledFor(verbose_logging_level):
|
|
self._log(verbose_logging_level, message, args, **kws)
|
|
|
|
logging.Logger.verbose = verbose
|
|
|
|
root_logger = logging.getLogger(__name__)
|
|
root_logger.setLevel(logging.DEBUG)
|
|
args[KEY_LOGGER] = root_logger
|
|
|
|
logging_formatter = logging.Formatter(LOGGING_FORMAT)
|
|
stdout_formatter = logging.Formatter(STDOUT_FORMAT)
|
|
|
|
log_to_std_out = logging.StreamHandler(sys.stdout)
|
|
if args[KEY_VERBOSE]:
|
|
log_to_std_out.setLevel(verbose_logging_level)
|
|
else:
|
|
log_to_std_out.setLevel(logging.INFO)
|
|
log_to_std_out.setFormatter(stdout_formatter)
|
|
|
|
log_file_name = "GreengrassDeviceSetup-{}.log".format(datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
|
|
args[KEY_LOG_FILE] = log_file_name
|
|
|
|
log_to_file = logging.FileHandler(os.path.join(args[KEY_LOG_PATH], log_file_name))
|
|
log_to_file.setLevel(logging.DEBUG)
|
|
log_to_file.setFormatter(logging_formatter)
|
|
|
|
root_logger.addHandler(log_to_std_out)
|
|
root_logger.addHandler(log_to_file)
|
|
|
|
@classmethod
|
|
def log_config_info_source(cls, args):
|
|
logger = args.get(KEY_LOGGER)
|
|
if args[KEY_IS_CONFIG_FILE_EXIST] and args[KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN]:
|
|
logger.debug("GreengrassDeviceSetup.config.info is found. Continuing with previous configuration values.")
|
|
elif args[KEY_IS_CONFIG_FILE_EXIST] and not args[KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN]:
|
|
logger.debug("Discarding the existing GreengrassDeviceSetup.config.info. Starting with new configuration.")
|
|
else:
|
|
logger.debug("Starting GreengrassDeviceSetup with new configuration.")
|
|
|
|
@classmethod
|
|
def set_deploy_time_out(cls, args):
|
|
global CONFIG_DEPLOYMENT_TIMEOUT
|
|
CONFIG_DEPLOYMENT_TIMEOUT = args[KEY_DEPLOYMENT_TIMEOUT]
|
|
|
|
# Currently, GreengrassDeviceSetup only support for the latest version. Remove this method, if GreengrassDeviceSetup support more version in future.
|
|
@classmethod
|
|
def validate_latest_ggc_version(cls, args):
|
|
if args[KEY_GGC_VERSION] != LATEST_GGC_VERSION_AWARE:
|
|
err_msg = "Currently, GreengrassDeviceSetup only supports the latest " \
|
|
"GGC version: {}.".format(LATEST_GGC_VERSION_AWARE)
|
|
raise StepError(code=StepError.ERR_ARG_COLLECTION, message=err_msg)
|
|
|
|
# Python2.7 is on its deprecation path. Disable this option, if python2.7 is running.
|
|
@classmethod
|
|
def disable_helloworld_lambda_if_runtime_python27(cls, args):
|
|
if sys.version_info.major == 2 and args[KEY_HAS_HELLO_WORLD_LAMBDA] is True:
|
|
err_msg = "The HelloWorld Lambda function requires Python 3.7, but GreengrassDeviceSetup was unable to " \
|
|
"install Python 3.7. To include the function, you must install Python 3.7 manually and restart " \
|
|
"the script. To omit the function, restart the script and enter 'no' " \
|
|
"when prompted to include the function."
|
|
raise StepError(code=StepError.ERR_ARG_COLLECTION, message=err_msg)
|
|
|
|
|
|
class EnvironmentPreValidation(Step):
|
|
|
|
def __init__(self, args):
|
|
super(EnvironmentPreValidation, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
logger.info("Validating the device environment...")
|
|
|
|
EnvironmentPreValidation.validate_platform(self._args)
|
|
EnvironmentPreValidation.validate_required_linux_cmd(self._args)
|
|
EnvironmentPreValidation.clarify_pkg_management_tool(self._args)
|
|
|
|
logger.info("Validation of the device environment is complete.\n")
|
|
|
|
return self._args
|
|
|
|
@staticmethod
|
|
def validate_platform(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
msg = "Validating the device platform..."
|
|
logger.verbose(msg)
|
|
|
|
device_platform = platform.platform()
|
|
args[KEY_DEVICE_PLATFORM] = device_platform
|
|
|
|
for supported_platform in SUPPORTED_PLATFORMS:
|
|
if supported_platform in device_platform:
|
|
args[KEY_ARCHITECTURE] = supported_platform
|
|
msg = "Found supported platform: {}.".format(device_platform)
|
|
logger.verbose(msg)
|
|
return
|
|
|
|
err_msg = "Platform {} not supported".format(device_platform)
|
|
logger.error(err_msg)
|
|
raise StepError(code=StepError.ERR_ENV_PREVALIDATE, message=err_msg)
|
|
|
|
@classmethod
|
|
def validate_required_linux_cmd(cls, args):
|
|
logger = args.get(KEY_LOGGER)
|
|
msg = "Validating required Linux commands..."
|
|
logger.verbose(msg)
|
|
|
|
# all required linux cmd should be there
|
|
for cmd in REQUIRED_LINUX_CMD:
|
|
if not Step.cmd_exist(cmd):
|
|
err_msg = "Required Linux command {} not found.".format(cmd)
|
|
logger.error(err_msg)
|
|
raise StepError(code=StepError.ERR_ENV_PREVALIDATE, message=err_msg)
|
|
|
|
@staticmethod
|
|
def clarify_pkg_management_tool(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
msg = "Validating the package management tool..."
|
|
logger.verbose(msg)
|
|
|
|
for pkg_management_tool in SUPPORTED_PKG_MANAGEMENT_TOOLS:
|
|
# any one of the supported pkg tool is good enough
|
|
if Step.cmd_exist(pkg_management_tool):
|
|
args[KEY_PKG_MANAGEMENT_TOOL] = pkg_management_tool
|
|
msg = "Using package management tool: {}.".format(pkg_management_tool)
|
|
logger.verbose(msg)
|
|
return
|
|
|
|
err_msg = "Not able to find any of the supported package management tools: {}." \
|
|
.format(SUPPORTED_PKG_MANAGEMENT_TOOLS)
|
|
logger.error(err_msg)
|
|
raise StepError(code=StepError.ERR_ENV_PREVALIDATE, message=err_msg)
|
|
|
|
|
|
class GreengrassEnvironmentBootstrap(Step):
|
|
|
|
def __init__(self, args):
|
|
super(GreengrassEnvironmentBootstrap, self).__init__(args)
|
|
# step sequence matters
|
|
self._steps = [
|
|
UserGroupBootstrap,
|
|
HardSoftLinkProtectionBootstrap,
|
|
CGroupBootstrap,
|
|
GGCDependencyCheck,
|
|
]
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
logger.info("Running the Greengrass environment setup...")
|
|
|
|
for step in self._steps:
|
|
self._args = step(self._args).execute()
|
|
|
|
logger.info("The Greengrass environment setup is complete.\n")
|
|
|
|
return self._args
|
|
|
|
|
|
def check_if_user_exists(username):
|
|
try:
|
|
pwd.getpwnam(username)
|
|
return True
|
|
except KeyError:
|
|
return False
|
|
|
|
|
|
def check_if_group_exists(groupname):
|
|
try:
|
|
grp.getgrnam(groupname)
|
|
return True
|
|
except KeyError:
|
|
return False
|
|
|
|
|
|
class UserGroupBootstrap(Step):
|
|
KEY_USER = "user"
|
|
KEY_GROUP = "group"
|
|
ADD_TARGET_RUNBOOK = {
|
|
KEY_USER: ADD_USER,
|
|
KEY_GROUP: ADD_GROUP,
|
|
}
|
|
TARGET_ADD_RUNBOOK = {
|
|
KEY_USER: USER_ADD,
|
|
KEY_GROUP: GROUP_ADD,
|
|
}
|
|
ARGS_KEY_RUNBOOK = {
|
|
KEY_USER: KEY_ADD_USER_TOOL,
|
|
KEY_GROUP: KEY_ADD_GROUP_TOOL,
|
|
}
|
|
VERIFY_TARGET_EXISTS_RUNBOOK = {
|
|
KEY_USER: check_if_user_exists,
|
|
KEY_GROUP: check_if_group_exists,
|
|
}
|
|
|
|
def __init__(self, args):
|
|
super(UserGroupBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the Greengrass access identity: 'ggc_user' and 'ggc_group'..."
|
|
logger.verbose(msg)
|
|
|
|
UserGroupBootstrap.bootstrap_user_group(self._args)
|
|
return self._args
|
|
|
|
@classmethod
|
|
def bootstrap_user_group(cls, args):
|
|
cls._bootstrap_user(args)
|
|
cls._bootstrap_group(args)
|
|
|
|
@classmethod
|
|
def _bootstrap_user(cls, args):
|
|
cls._bootstrap_target(args, cls.KEY_USER)
|
|
|
|
@classmethod
|
|
def _bootstrap_group(cls, args):
|
|
cls._bootstrap_target(args, cls.KEY_GROUP)
|
|
|
|
@classmethod
|
|
def _bootstrap_target(cls, args, target):
|
|
# check if we already configured target. If not, proceed:
|
|
# check if we have [target]add or add[target]
|
|
# if not, try install any of them, fail if nothing can be installed
|
|
# configure target
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Checking if {} has already been configured...".format(target))
|
|
verify_exist_func = cls.VERIFY_TARGET_EXISTS_RUNBOOK.get(target)
|
|
if verify_exist_func("ggc_{}".format(target)):
|
|
logger.debug("{} has already been configured.".format(target))
|
|
return
|
|
|
|
logger.debug("{} has not been configured. Proceeding to install configuration tools.".format(target))
|
|
target_add = cls.TARGET_ADD_RUNBOOK.get(target)
|
|
add_target = cls.ADD_TARGET_RUNBOOK.get(target)
|
|
target_args_key = cls.ARGS_KEY_RUNBOOK.get(target)
|
|
|
|
if Step.cmd_exist(target_add):
|
|
logger.debug("Tool {} exists.".format(target_add))
|
|
args[target_args_key] = target_add
|
|
elif Step.cmd_exist(add_target):
|
|
logger.debug("Tool {} exists.".format(add_target))
|
|
args[target_args_key] = add_target
|
|
else:
|
|
logger.debug("Installing configuration tools...")
|
|
pkg_tool_runbook = INSTALL_RUNBOOKS.get(args[KEY_PKG_MANAGEMENT_TOOL])
|
|
cmd_install_target_add = pkg_tool_runbook.get(target_add)
|
|
cmd_install_add_target = pkg_tool_runbook.get(add_target)
|
|
installed = False
|
|
|
|
if not installed and cmd_install_target_add is not None:
|
|
ret_code, _, _ = Step.run_linux_cmd(cmd_install_target_add)
|
|
logger.debug("Running command: {}.".format(cmd_install_target_add))
|
|
if ret_code == 0:
|
|
args[target_args_key] = target_add
|
|
logger.debug("Installed tool {}.".format(target_add))
|
|
installed = True
|
|
logger.debug("Installing tool {} failed.".format(target_add))
|
|
|
|
if not installed and cmd_install_add_target is not None:
|
|
ret_code, _, _ = Step.run_linux_cmd(cmd_install_add_target)
|
|
logger.debug("Running command: {}.".format(cmd_install_add_target))
|
|
if ret_code == 0:
|
|
args[target_args_key] = add_target
|
|
logger.debug("Installed tool {}.".format(add_target))
|
|
installed = True
|
|
logger.debug("Installing tool {} failed.".format(add_target))
|
|
|
|
if not installed:
|
|
err_msg = "Not able to use {} to install {} or {}.".format(
|
|
args[KEY_PKG_MANAGEMENT_TOOL], target_add, add_target
|
|
)
|
|
logger.error(err_msg)
|
|
raise StepError(code=StepError.ERR_GG_ENV_BOOTSTRAP, message=err_msg)
|
|
|
|
cmd_add_ggc_target = "{} --system ggc_{}".format(args[target_args_key], target)
|
|
logger.debug("Running command: {}.".format(cmd_add_ggc_target))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_add_ggc_target,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="Not able to add ggc_{}.".format(target))
|
|
|
|
|
|
class HardSoftLinkProtectionBootstrap(Step):
|
|
SYSCTL_OVERRIDE_CONFIG = "/etc/sysctl.conf"
|
|
|
|
def __init__(self, args):
|
|
super(HardSoftLinkProtectionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring hardlink and softlink protection..."
|
|
logger.verbose(msg)
|
|
|
|
HardSoftLinkProtectionBootstrap.bootstrap_hard_soft_link_protection(self._args)
|
|
return self._args
|
|
|
|
@staticmethod
|
|
def bootstrap_hard_soft_link_protection(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
# we will make sure we always have an overridden configuration that enables hard/soft link protection
|
|
|
|
# skip if we already enabled hardlink protection
|
|
cmd_enable_hardlink_protection = \
|
|
"grep -q \"fs.protected_hardlinks = 1\" {0} || echo \"fs.protected_hardlinks = 1\" >> {0}".format(
|
|
HardSoftLinkProtectionBootstrap.SYSCTL_OVERRIDE_CONFIG,
|
|
)
|
|
# skip if we already enabled softlink protection
|
|
cmd_enable_softlink_protection = \
|
|
"grep -q \"fs.protected_symlinks = 1\" {0} || echo \"fs.protected_symlinks = 1\" >> {0}".format(
|
|
HardSoftLinkProtectionBootstrap.SYSCTL_OVERRIDE_CONFIG,
|
|
)
|
|
|
|
logger.debug("Running command: {}.".format(cmd_enable_hardlink_protection))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_enable_hardlink_protection,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="Not able to enable hardlink protection.")
|
|
|
|
logger.debug("Running command: {}.".format(cmd_enable_softlink_protection))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_enable_softlink_protection,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="Not able to enable softlink protection.")
|
|
|
|
# now we make sure this change takes effect immediately
|
|
cmd_set_sysctl_values = "sysctl -p"
|
|
logger.debug("Running command: {}.".format(cmd_set_sysctl_values))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_set_sysctl_values,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="Not able to make sysctl changes immediately effective.")
|
|
|
|
|
|
class CGroupBootstrap(Step):
|
|
CGROUPFS_MOUNT_SH = "./cgroupfs-mount.sh"
|
|
|
|
def __init__(self, args):
|
|
super(CGroupBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring cgroups..."
|
|
logger.verbose(msg)
|
|
|
|
# pull down cgroupfs-mount script and run it
|
|
# if it fails, try enabling memory cgroups in boot file. This will require a restart.
|
|
linux_distribution = repr(distro.linux_distribution()).lower()
|
|
if not self._args.get(KEY_RECOVER_OR_REBOOT_FROM_LAST_RUN):
|
|
|
|
# Raspbian OS always require boot file edits and a restart to enable cgroups memory
|
|
if "raspbian" in linux_distribution or 'openwrt' in linux_distribution:
|
|
CGroupBootstrap.add_cgroup_mem_mount_to_boot_cmd(self._args)
|
|
else:
|
|
script_succeeds = CGroupBootstrap.run_cgroupfs_mount_script(self._args)
|
|
if not script_succeeds:
|
|
CGroupBootstrap.add_cgroup_mem_mount_to_boot_cmd(self._args)
|
|
else:
|
|
# What need to do after rebooting
|
|
if 'openwrt' in linux_distribution:
|
|
CGroupBootstrap.add_symlink_to_boot_cmd(self._args)
|
|
|
|
return self._args
|
|
|
|
@staticmethod
|
|
def run_cgroupfs_mount_script(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Configuring cgroups using cgroupfs mount script.")
|
|
|
|
try:
|
|
logger.debug("Downloading script from: {}.".format(CGROUPFS_MOUNT_SCRIPT_REMOTE_LOCATION))
|
|
urlretrieve(url=CGROUPFS_MOUNT_SCRIPT_REMOTE_LOCATION, filename=CGroupBootstrap.CGROUPFS_MOUNT_SH)
|
|
|
|
cmd_change_script_permissions = "chmod +x {}".format(CGroupBootstrap.CGROUPFS_MOUNT_SH)
|
|
logger.debug("Running command: {}.".format(cmd_change_script_permissions))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_change_script_permissions,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="Not able to change the permissions for cgroupfs-mount.sh.")
|
|
|
|
ret_code, std_out, std_err = Step.run_linux_cmd(CGroupBootstrap.CGROUPFS_MOUNT_SH)
|
|
logger.debug("Running command: {}.".format(CGroupBootstrap.CGROUPFS_MOUNT_SH))
|
|
logger.debug("Script output: stdout: {} stderr: {}".format(std_out, std_err))
|
|
|
|
return ret_code == 0
|
|
|
|
finally:
|
|
cmd_rm_script = "rm {}".format(CGroupBootstrap.CGROUPFS_MOUNT_SH)
|
|
logger.debug("Running command: {}.".format(cmd_rm_script))
|
|
# regardless of results, clean up the script once the execution is done.
|
|
Step.run_linux_cmd(cmd_rm_script)
|
|
|
|
@staticmethod
|
|
def add_cgroup_mem_mount_to_boot_cmd(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Not able to configure cgroups using cgroupfs mount script. Updating the boot cmdline file...")
|
|
|
|
# add if missing
|
|
cmd_string = "grep -qxF 'cgroup_enable=memory cgroup_memory=1' /boot/cmdline.txt || " \
|
|
"sed -i '$ s/$/ cgroup_enable=memory cgroup_memory=1/' /boot/cmdline.txt"
|
|
logger.debug("Running command: {}.".format(cmd_string))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_string,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="Not able to configure cgroup memory mount in boot file.")
|
|
logger.info("A reboot is required to make cgroups configuration change effective.")
|
|
build_config_info_file(args)
|
|
CGroupBootstrap.notify_reboot_and_exit()
|
|
|
|
@staticmethod
|
|
def notify_reboot_and_exit():
|
|
print("You must reboot your device manually and then restart GreengrassDeviceSetup.")
|
|
exit(0) # Use exit code 0 just to indicate that there is no failure and we just need a reboot
|
|
|
|
@staticmethod
|
|
def add_symlink_to_boot_cmd(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.verbose(
|
|
"The symlink is not consistent across reboot. Adding symlinks to the boot sequence...")
|
|
|
|
try:
|
|
os.symlink("/proc/self/fd/0", "/dev/stdin")
|
|
logger.verbose("Adding symlink of /proc/self/fd/0 -> /dev/stdin")
|
|
except:
|
|
logger.verbose("Failed to add symlink of /proc/self/fd/0 -> /dev/stdin")
|
|
|
|
try:
|
|
os.symlink("/proc/self/fd/1", "/dev/stdout")
|
|
logger.verbose("Adding symlink of /proc/self/fd/1 -> /dev/stdout")
|
|
except:
|
|
logger.verbose("Failed to add symlink of /proc/self/fd/1 -> /dev/stdout")
|
|
|
|
try:
|
|
os.symlink("/proc/self/fd/2", "/dev/stderr")
|
|
logger.verbose("Adding symlink of /proc/self/fd/2 -> /dev/stderr")
|
|
except:
|
|
logger.verbose("Failed to add symlink of /proc/self/fd/2 -> /dev/stderr")
|
|
|
|
|
|
class GGCDependencyCheck(Step):
|
|
RUN_CHECKER = "./check_ggc_dependencies"
|
|
|
|
def __init__(self, args):
|
|
super(GGCDependencyCheck, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Running the Greengrass dependency checker..."
|
|
logger.verbose(msg)
|
|
|
|
core_major_minor_version = None
|
|
try:
|
|
core_major_minor_version = GGCDependencyCheck.parse_ggc_major_minor_version(self._args[KEY_GGC_VERSION])
|
|
GGCDependencyCheck.prepare_kernel_config_file_if_missing(self._args)
|
|
GGCDependencyCheck.download_checker(self._args, core_major_minor_version)
|
|
GGCDependencyCheck.run_checker(self._args, core_major_minor_version)
|
|
finally:
|
|
# regardless of results, clean up the checker files once the execution is done.
|
|
if core_major_minor_version is not None:
|
|
GGCDependencyCheck.clean_up_checker_files(self._args, core_major_minor_version)
|
|
return self._args
|
|
|
|
@staticmethod
|
|
def parse_ggc_major_minor_version(version):
|
|
# at this point, "matched" should never be none, otherwise it fails in earlier GreengrassDeviceSetup stages
|
|
matched = re.match(GGC_VERSION_REGEX, version)
|
|
return "{}.{}.x".format(matched.group(1), matched.group(2))
|
|
|
|
@staticmethod
|
|
def prepare_kernel_config_file_if_missing(args):
|
|
linux_distribution = repr(distro.linux_distribution()).lower()
|
|
if "raspbian" in linux_distribution:
|
|
logger = args.get(KEY_LOGGER)
|
|
cmd_mount_cgroup = "modprobe configs"
|
|
logger.debug("Running command: {}.".format(cmd_mount_cgroup))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_mount_cgroup,
|
|
err_code=StepError.ERR_MOUNT_CGROUP,
|
|
err_msg="The file '/proc/config.gz' was not found.")
|
|
|
|
@staticmethod
|
|
def download_checker(args, major_minor_version):
|
|
logger = args.get(KEY_LOGGER)
|
|
checker_remote_location = GGC_DEPENDENCY_CHECKER_REMOTE_LOCATION_FORMAT.format(major_minor_version)
|
|
logger.debug("Downloading GGC dependency checker from: {}.".format(checker_remote_location))
|
|
urlretrieve(url=checker_remote_location, filename=GGC_DEPENDENCY_CHECKER_ZIP_FORMAT.format(major_minor_version))
|
|
|
|
@staticmethod
|
|
def run_checker(args, major_minor_version):
|
|
logger = args.get(KEY_LOGGER)
|
|
checker_dir = GGC_DEPENDENCY_CHECKER_FORMAT.format(major_minor_version)
|
|
checker_zip = GGC_DEPENDENCY_CHECKER_ZIP_FORMAT.format(major_minor_version)
|
|
|
|
logger.debug("Unpacking {}.".format(checker_zip))
|
|
with ZipFile(checker_zip, 'r') as zf:
|
|
zf.extractall()
|
|
|
|
# we need "cd" here because the dependency checker script assumes all sub-scripts are under the same directory
|
|
with ChangeDirectory("./{}".format(checker_dir)):
|
|
# TODO: See if we can fix some issues reported from dependency checker so that customers don't need to do it
|
|
# https://issues.amazon.com/issues/GG-25320
|
|
cmd_chmod_x = "chmod +x {}".format(GGCDependencyCheck.RUN_CHECKER)
|
|
logger.debug("Running command: {}.".format(cmd_chmod_x))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_chmod_x,
|
|
err_code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
err_msg="GreengrassDeviceSetup cannot continue: "
|
|
"Not able to add execute permission to GGC dependency checker.")
|
|
|
|
checker_result_file = os.path.abspath(os.path.join(os.getcwd(), "checker_result.txt"))
|
|
cmd_run_gg_dependency_checker = GGCDependencyCheck.RUN_CHECKER + " > " + checker_result_file
|
|
ret_code, std_out, std_err = Step.run_linux_cmd(cmd_run_gg_dependency_checker)
|
|
dependency_checker_result_info = GGCDependencyCheck.read_dependency_checker_result(checker_result_file)
|
|
if ret_code != 0:
|
|
for info in dependency_checker_result_info:
|
|
print(info)
|
|
raise StepError(code=ret_code, message="Error" + " Stdout: {} Stderr: {}".format(std_out, std_err))
|
|
GGCDependencyCheck.parse_dependency_checker_result(args, dependency_checker_result_info)
|
|
|
|
@staticmethod
|
|
def read_dependency_checker_result(checker_result_file):
|
|
with open(checker_result_file, "r") as f:
|
|
res_info = f.readlines()
|
|
return res_info
|
|
|
|
@staticmethod
|
|
def parse_dependency_checker_result(args, res_info):
|
|
user_group_info = []
|
|
lambda_isolation_mode_info = []
|
|
systemd_info_list = []
|
|
user_group_flag = False
|
|
lambda_flag = False
|
|
systemd_flag = False
|
|
|
|
for line in res_info:
|
|
if "----User and group----" in line:
|
|
user_group_flag = True
|
|
continue
|
|
if "----(Optional) Greengrass container dependency check----" in line:
|
|
user_group_flag = False
|
|
|
|
if "Note:" in line:
|
|
systemd_flag = True
|
|
continue
|
|
if systemd_flag and ("Missing optional dependencies:" in line or
|
|
"Missing required dependencies:" in line or
|
|
"(Optional) Greengrass container dependencies" in line or
|
|
"Errors:" in line or
|
|
"Supported lambda isolation modes:" in line):
|
|
systemd_flag = False
|
|
|
|
if "Supported lambda isolation modes:" in line:
|
|
lambda_flag = True
|
|
continue
|
|
if "---Exit status----" in line:
|
|
lambda_flag = False
|
|
|
|
if user_group_flag:
|
|
user_group_info.append(line)
|
|
if lambda_flag:
|
|
lambda_isolation_mode_info.append(line)
|
|
if systemd_flag:
|
|
systemd_info_list.append(line)
|
|
|
|
systemd_info = " ".join(systemd_info_list)
|
|
GGCDependencyCheck.check_whether_use_systemd(args, systemd_info)
|
|
GGCDependencyCheck.check_both_ggc_user_and_ggc_group_exist(user_group_info)
|
|
GGCDependencyCheck.check_lambda_isolation_mode(lambda_isolation_mode_info)
|
|
|
|
@staticmethod
|
|
def check_whether_use_systemd(args, systemd_info):
|
|
if "kernel uses 'systemd'" in systemd_info:
|
|
args[KEY_USE_SYSTEMD] = "yes"
|
|
if "kernel does NOT use 'systemd'" in systemd_info:
|
|
args[KEY_USE_SYSTEMD] = "no"
|
|
|
|
@staticmethod
|
|
def check_both_ggc_user_and_ggc_group_exist(user_group_info):
|
|
ggc_user_flag = False
|
|
ggc_group_flag = False
|
|
for line in user_group_info:
|
|
if "ggc_user" in line and "Present" in line:
|
|
ggc_user_flag = True
|
|
if "ggc_group" in line and "Present" in line:
|
|
ggc_group_flag = True
|
|
if not ggc_user_flag or not ggc_group_flag:
|
|
raise StepError(code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
message="There is no ggc_user/ggc_group to run Greengrass core.")
|
|
|
|
@staticmethod
|
|
def check_lambda_isolation_mode(lambda_isolation_mode_info):
|
|
for line in lambda_isolation_mode_info:
|
|
if "Greengrass Container" in line and "Not supported" in line:
|
|
raise StepError(code=StepError.ERR_GG_ENV_BOOTSTRAP,
|
|
message="GreengrassDeviceSetup cannot continue: "
|
|
"Greengrass containers are not supported on this platform.")
|
|
|
|
@staticmethod
|
|
def clean_up_checker_files(args, major_minor_version):
|
|
logger = args.get(KEY_LOGGER)
|
|
cmd_remove_zip_file = "rm ./{}".format(GGC_DEPENDENCY_CHECKER_ZIP_FORMAT.format(major_minor_version))
|
|
cmd_remove_zip_dir = "rm -rf ./{}".format(GGC_DEPENDENCY_CHECKER_FORMAT.format(major_minor_version))
|
|
|
|
logger.debug("Running command: {}.".format(cmd_remove_zip_file))
|
|
Step.run_linux_cmd(cmd_remove_zip_file)
|
|
|
|
logger.debug("Running command: {}.".format(cmd_remove_zip_dir))
|
|
Step.run_linux_cmd(cmd_remove_zip_dir)
|
|
|
|
|
|
class GreengrassCloudBootstrap(Step):
|
|
|
|
def __init__(self, args):
|
|
super(GreengrassCloudBootstrap, self).__init__(args)
|
|
# step sequence matters
|
|
self._steps = [
|
|
CloudPermissionBootstrap,
|
|
CoreDefinitionBootstrap,
|
|
LoggerDefinitionBootstrap,
|
|
FunctionDefinitionBootstrap,
|
|
SubscriptionDefinitionBootstrap,
|
|
GroupDefinitionBootstrap
|
|
]
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
logger.info("Configuring cloud-based Greengrass group management...")
|
|
|
|
self._args[KEY_BOTO_SESSION] = boto3.Session(
|
|
aws_access_key_id=self._args[KEY_AWS_ACCESS_KEY_ID],
|
|
aws_secret_access_key=self._args[KEY_AWS_SECRET_ACCESS_KEY],
|
|
aws_session_token=self._args[KEY_AWS_SESSION_TOKEN],
|
|
region_name=self._args.get(KEY_AWS_REGION),
|
|
)
|
|
|
|
for step in self._steps:
|
|
self._args = step(self._args).execute()
|
|
logger.info("The Greengrass group configuration is complete.\n")
|
|
return self._args
|
|
|
|
|
|
class CloudPermissionBootstrap(Step):
|
|
KEY_ROLE = "Role"
|
|
KEY_ARN = "Arn"
|
|
KEY_ROLE_ARN = KEY_ROLE + KEY_ARN
|
|
RC_NOT_FOUND = 404
|
|
|
|
def __init__(self, args):
|
|
super(CloudPermissionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the 'Greengrass_ServiceRole'..."
|
|
logger.verbose(msg)
|
|
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
if not CloudPermissionBootstrap.is_gg_account_service_role_attached(self._args, session):
|
|
CloudPermissionBootstrap.configure_gg_account_service_role(self._args, session)
|
|
logger.debug("Permissions for the Greengrass service role were configured.")
|
|
return self._args
|
|
|
|
@classmethod
|
|
def is_gg_account_service_role_attached(cls, args, session):
|
|
gg_client = session.client(GREENGRASS)
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Checking whether a Greengrass service role has been attached to the account...")
|
|
try:
|
|
resp = gg_client.get_service_role_for_account()
|
|
args[KEY_GG_ACCOUNT_SERVICE_ROLE] = resp[cls.KEY_ROLE_ARN]
|
|
logger.debug("A Greengrass service role was attached to the account.")
|
|
return True
|
|
except ClientError as e:
|
|
err_code = e.response[KEY_RESPONSE_METADATA][KEY_HTTP_STATUS_CODE]
|
|
logger.debug(e.response)
|
|
if err_code != cls.RC_NOT_FOUND: # 404s are handled by the following logic to attach the missing role
|
|
raise e
|
|
logger.debug("There is no Greengrass Service Role attached to this AWS account. "
|
|
"Attaching a service role to the account...")
|
|
return False
|
|
|
|
@classmethod
|
|
def configure_gg_account_service_role(cls, args, session):
|
|
logger = args.get(KEY_LOGGER)
|
|
iam_client = session.client(IAM)
|
|
gg_client = session.client(GREENGRASS)
|
|
region = args[KEY_AWS_REGION]
|
|
|
|
# create a role under with proper trust entity
|
|
logger.debug("Creating a Greengrass service role for the account.")
|
|
role_name = "GreengrassServiceRole_" + Step.get_random_string(5)
|
|
assume_role_policy = {
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Effect": "Allow",
|
|
"Principal": {
|
|
"Service": [
|
|
"greengrass.amazonaws.com"
|
|
]
|
|
},
|
|
"Action": "sts:AssumeRole"
|
|
}
|
|
]
|
|
}
|
|
create_role_resp = iam_client.create_role(
|
|
RoleName=role_name,
|
|
AssumeRolePolicyDocument=json.dumps(assume_role_policy),
|
|
)
|
|
role_arn = create_role_resp[cls.KEY_ROLE][cls.KEY_ARN]
|
|
logger.debug("A service role was successfully created.")
|
|
# attach aws managed Greengrass resource policy to this role
|
|
iam_client.attach_role_policy(
|
|
RoleName=role_name,
|
|
PolicyArn="arn:{}:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy"
|
|
.format(PARTITION_OVERRIDES.get(region, AWS)),
|
|
)
|
|
|
|
# attach this role as gg service role
|
|
logger.debug("Attaching the service role to the account.")
|
|
gg_client.associate_service_role_to_account(
|
|
RoleArn=role_arn,
|
|
)
|
|
|
|
# capture arn for this role
|
|
args[KEY_GG_ACCOUNT_SERVICE_ROLE] = role_arn
|
|
|
|
|
|
class CoreDefinitionBootstrap(Step):
|
|
KEY_CERT_ID = "certificateId"
|
|
KEY_CERT_ARN = "certificateArn"
|
|
KEY_THING_ARN = "thingArn"
|
|
KEY_POLICY_NAME = "policyName"
|
|
KEY_CERT_PEM = "certificatePem"
|
|
KEY_KEY_PAIR = "keyPair"
|
|
KEY_PRIV_KEY = "PrivateKey"
|
|
KEY_LATEST_VER_ARN = "LatestVersionArn"
|
|
POLICY_DOCUMENT = "policyDocument"
|
|
RC_ALREADY_EXIST = 409
|
|
|
|
def __init__(self, args):
|
|
super(CoreDefinitionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the core definition..."
|
|
logger.verbose(msg)
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
CoreDefinitionBootstrap.prepare_core_thing(self._args, session)
|
|
CoreDefinitionBootstrap.prepare_core_definition(self._args, session)
|
|
logger.debug("The core definition was configured.")
|
|
return self._args
|
|
|
|
@classmethod
|
|
def prepare_core_thing(cls, args, session):
|
|
logger = args.get(KEY_LOGGER)
|
|
iot_client = session.client(IOT)
|
|
region = args[KEY_AWS_REGION]
|
|
core_name = args[KEY_CORE_NAME]
|
|
|
|
# create core iot thing and its credentials
|
|
logger.debug("Creating an IoT thing and credentials for the Greengrass core.")
|
|
core_thing = iot_client.create_thing(thingName=core_name)
|
|
key_cert = iot_client.create_keys_and_certificate(setAsActive=True)
|
|
thing_arn = core_thing[cls.KEY_THING_ARN]
|
|
cert_arn = key_cert[cls.KEY_CERT_ARN]
|
|
logger.debug("The IoT thing for Greengrass core was successfully created.")
|
|
|
|
# associate core iot thing with its cert
|
|
logger.debug("Attaching the certificate to the Greengrass core...")
|
|
iot_client.attach_thing_principal(
|
|
thingName=core_name,
|
|
principal=cert_arn,
|
|
)
|
|
logger.debug("The certificate was successfully attached to Greengrass core.")
|
|
|
|
# create iot policy
|
|
logger.debug("Creating an IoT policy for the Greengrass core...")
|
|
core_policy_doc = {
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Effect": "Allow",
|
|
# iot data plane
|
|
"Action": ["iot:Publish", "iot:Subscribe", "iot:Connect", "iot:Receive", "iot:GetThingShadow",
|
|
"iot:DeleteThingShadow", "iot:UpdateThingShadow"],
|
|
"Resource": ["arn:{}:iot:{}:*:*".format(PARTITION_OVERRIDES.get(region, AWS), region)]
|
|
},
|
|
{
|
|
"Effect": "Allow",
|
|
# Greengrass data plane
|
|
"Action": ["greengrass:AssumeRoleForGroup", "greengrass:CreateCertificate",
|
|
"greengrass:GetConnectivityInfo", "greengrass:GetDeployment",
|
|
"greengrass:GetDeploymentArtifacts", "greengrass:UpdateConnectivityInfo",
|
|
"greengrass:UpdateCoreDeploymentStatus"],
|
|
"Resource": ["*"]
|
|
}
|
|
]
|
|
}
|
|
|
|
policy_name = "{}_basic_policy".format(core_name)
|
|
try:
|
|
iot_client.create_policy(
|
|
policyName=policy_name,
|
|
policyDocument=json.dumps(core_policy_doc)
|
|
)
|
|
logger.debug("The IoT policy for the Greengrass core was successfully created.")
|
|
except ClientError as e:
|
|
err_code = e.response[KEY_RESPONSE_METADATA][KEY_HTTP_STATUS_CODE]
|
|
if err_code != cls.RC_ALREADY_EXIST:
|
|
raise e
|
|
# check if document in cloud is same with what we have
|
|
policy = iot_client.get_policy(policyName=policy_name)
|
|
policy_document = json.loads(policy[cls.POLICY_DOCUMENT])
|
|
recursive_sort(policy_document)
|
|
recursive_sort(core_policy_doc)
|
|
if core_policy_doc == policy_document:
|
|
logger.debug("The policy for the Greengrass core already exist, skipping this step.")
|
|
else:
|
|
raise StepError(code=StepError.ERR_GG_CLOUD_BOOTSTRAP,
|
|
message="The policy {} for the Greengrass core already exists but "
|
|
"it has different content than expected. "
|
|
"You should manually rename or delete this policy.".format(policy_name))
|
|
|
|
# associate iot policy with core cert
|
|
logger.debug("Attaching the policy to certificate...")
|
|
iot_client.attach_policy(
|
|
policyName=policy_name,
|
|
target=cert_arn,
|
|
)
|
|
logger.debug("The policy was successfully attached to the Greengrass core.")
|
|
|
|
# capture info in args
|
|
args[KEY_CORE_THING_ARN] = thing_arn
|
|
args[KEY_CORE_CERT_ARN] = cert_arn
|
|
args[KEY_CORE_CERT_ID] = key_cert[cls.KEY_CERT_ID]
|
|
args[KEY_CORE_CERT_PEM] = key_cert[cls.KEY_CERT_PEM]
|
|
args[KEY_CORE_PRIV_KEY] = key_cert[cls.KEY_KEY_PAIR][cls.KEY_PRIV_KEY]
|
|
|
|
@classmethod
|
|
def prepare_core_definition(cls, args, session):
|
|
logger = args.get(KEY_LOGGER)
|
|
gg_client = session.client(GREENGRASS)
|
|
core_name = args[KEY_CORE_NAME]
|
|
cert_arn = args[KEY_CORE_CERT_ARN]
|
|
thing_arn = args[KEY_CORE_THING_ARN]
|
|
|
|
# create core definition with version
|
|
logger.debug("Creating the core definition with an initial version.")
|
|
initial_core_definition_version = {
|
|
'Cores': [
|
|
{
|
|
'Id': core_name,
|
|
'CertificateArn': cert_arn,
|
|
'SyncShadow': False,
|
|
'ThingArn': thing_arn,
|
|
}
|
|
]
|
|
}
|
|
core_definition = gg_client.create_core_definition(
|
|
Name="{}_def".format(core_name),
|
|
InitialVersion=initial_core_definition_version,
|
|
)
|
|
logger.debug("The core definition was successfully created.")
|
|
# capture info in args
|
|
args[KEY_CORE_DEF_VER_ARN] = core_definition[cls.KEY_LATEST_VER_ARN]
|
|
|
|
|
|
class FunctionDefinitionBootstrap(Step):
|
|
TMP_DIR = tempfile.gettempdir()
|
|
HELLO_WORLD_PY = "greengrassHelloWorld.py"
|
|
GG_PY_SDK_ZIP = "greengrass-core-python-sdk.zip"
|
|
HELLO_WORLD_LAMBDA_ZIP = "hello_world_python_lambda.zip"
|
|
UNZIPPED_GG_PY_SDK_DIR = "aws-greengrass-core-sdk-python-master"
|
|
GG_PY_SDK_DIR = "greengrasssdk"
|
|
KEY_LAMBDA_ARN = 'FunctionArn'
|
|
KEY_VERSION = "Version"
|
|
KEY_LATEST_VER_ARN = 'LatestVersionArn'
|
|
KEY_ROLE = 'Role'
|
|
KEY_ARN = 'Arn'
|
|
|
|
def __init__(self, args):
|
|
super(FunctionDefinitionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
if self._args[KEY_HAS_HELLO_WORLD_LAMBDA]: # only do the following if a hello-world lambda is requested
|
|
msg = "Configuring the function definition..."
|
|
logger.verbose(msg)
|
|
|
|
try:
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
|
|
FunctionDefinitionBootstrap.prepare_code_package(self._args)
|
|
lambda_execution_role_name = FunctionDefinitionBootstrap.prepare_lambda_execution_role(self._args,
|
|
session)
|
|
|
|
with yaspin().shark:
|
|
FunctionDefinitionBootstrap.prepare_lambda(self._args, session, lambda_execution_role_name)
|
|
|
|
FunctionDefinitionBootstrap.prepare_function_definition(self._args, session)
|
|
logger.debug("The function definition was configured.")
|
|
|
|
finally:
|
|
FunctionDefinitionBootstrap.clean_up(self._args)
|
|
return self._args
|
|
|
|
@classmethod
|
|
def prepare_lambda_execution_role(cls, args, session):
|
|
logger = args[KEY_LOGGER]
|
|
# create lambda execution role
|
|
region = args[KEY_AWS_REGION]
|
|
iam_client = session.client(IAM)
|
|
lambda_execution_role_name = "LambdaRole_" + Step.get_random_string(5)
|
|
logger.debug("Creating an execution role: {} for the HelloWorld Lambda function.".format(lambda_execution_role_name))
|
|
assume_role_policy = {
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Effect": "Allow",
|
|
"Principal": {
|
|
"Service": [
|
|
"lambda.amazonaws.com"
|
|
]
|
|
},
|
|
"Action": "sts:AssumeRole"
|
|
}
|
|
]
|
|
}
|
|
|
|
create_lambda_execution_role = iam_client.create_role(
|
|
RoleName=lambda_execution_role_name,
|
|
AssumeRolePolicyDocument=json.dumps(assume_role_policy),
|
|
)
|
|
logger.debug("The execution role was successfully created.")
|
|
lambda_execution_role_arn = create_lambda_execution_role[cls.KEY_ROLE][cls.KEY_ARN]
|
|
|
|
# Attach a policy to the lambda execution role
|
|
logger.debug("Attaching a policy to the Lambda execution role...")
|
|
iam_client.attach_role_policy(
|
|
RoleName=lambda_execution_role_name,
|
|
PolicyArn="arn:{}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole".format(
|
|
PARTITION_OVERRIDES.get(region, AWS)),
|
|
)
|
|
|
|
logger.debug("A policy was successfully attached to the Lambda execution role.")
|
|
return lambda_execution_role_arn
|
|
|
|
@classmethod
|
|
def prepare_code_package(cls, args):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Preparing the files for the HelloWorld function...")
|
|
|
|
with ChangeDirectory(cls.TMP_DIR):
|
|
# download gg python sdk and unzip
|
|
urlretrieve(url=LATEST_GG_PYTHON_SDK_REMOTE_LOCATION, filename="./" + cls.GG_PY_SDK_ZIP)
|
|
with ZipFile(cls.GG_PY_SDK_ZIP, "r") as zf:
|
|
zf.extractall()
|
|
|
|
# construct Greengrass hello world lambda package
|
|
with ChangeDirectory("./" + cls.UNZIPPED_GG_PY_SDK_DIR):
|
|
# download gg hello-world lambda
|
|
urlretrieve(url=LATEST_GG_HELLO_WORLD_LAMBDA_REMOTE_LOCATION,
|
|
filename="./" + cls.HELLO_WORLD_PY)
|
|
# zip up the required files/directories
|
|
with ZipFile(cls.HELLO_WORLD_LAMBDA_ZIP, "w") as lambda_pkg:
|
|
# add hello world py
|
|
lambda_pkg.write(cls.HELLO_WORLD_PY)
|
|
# add gg py sdk
|
|
for root, dirs, files in os.walk(cls.GG_PY_SDK_DIR):
|
|
for file in files:
|
|
lambda_pkg.write(os.path.join(root, file))
|
|
|
|
logger.debug("The code package for the HelloWorld function was created.")
|
|
|
|
@classmethod
|
|
def prepare_lambda(cls, args, session, role_arn):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Creating the HelloWorld function in AWS Lambda.")
|
|
with ChangeDirectory(os.path.join(cls.TMP_DIR, cls.UNZIPPED_GG_PY_SDK_DIR)):
|
|
with open(cls.HELLO_WORLD_LAMBDA_ZIP, "rb") as f:
|
|
lambda_client = session.client(LAMBDA)
|
|
zip_bytes = f.read()
|
|
functionName = "Greengrass_HelloWorld_" + Step.get_random_string(5)
|
|
try:
|
|
retry_template(cls.create_hello_world_lambda_function, args, lambda_client, functionName, role_arn,
|
|
zip_bytes)
|
|
except Exception as e:
|
|
logger.debug('Failed to create the function. Exceeded the maximum number of retries.')
|
|
raise e
|
|
|
|
@classmethod
|
|
def create_hello_world_lambda_function(cls, args, lambda_client, functionName, role_arn, zip_bytes):
|
|
logger = args.get(KEY_LOGGER)
|
|
try:
|
|
function_creation_resp = lambda_client.create_function(
|
|
FunctionName=functionName,
|
|
Runtime="python3.7",
|
|
Role=role_arn,
|
|
Handler="greengrassHelloWorld.function_handler",
|
|
Code={
|
|
"ZipFile": zip_bytes,
|
|
},
|
|
Timeout=25,
|
|
MemorySize=3008,
|
|
Publish=True,
|
|
)
|
|
args[KEY_HELLO_WORLD_LAMBDA_VERSIONED_ARN] = function_creation_resp[cls.KEY_LAMBDA_ARN] + ":" + \
|
|
function_creation_resp[cls.KEY_VERSION]
|
|
logger.debug("The HelloWorld function was successfully created.")
|
|
|
|
except Exception as e:
|
|
logger.debug('Failed to create the HelloWorld Lambda function. Retrying...')
|
|
raise Exception
|
|
|
|
@classmethod
|
|
def prepare_function_definition(cls, args, session):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Creating the function definition...")
|
|
|
|
gg_client = session.client(GREENGRASS)
|
|
initial_function_version = {
|
|
'Functions': [
|
|
{
|
|
'FunctionArn': args[KEY_HELLO_WORLD_LAMBDA_VERSIONED_ARN],
|
|
'FunctionConfiguration': {
|
|
'Executable': "greengrassHelloWorld.function_handler",
|
|
'MemorySize': 25600,
|
|
'Pinned': True,
|
|
'Timeout': 25,
|
|
},
|
|
'Id': 'LambdaHelloWorld' + Step.get_random_string(5),
|
|
},
|
|
]
|
|
}
|
|
|
|
# create the initial version of lambda function - "HelloWorld"
|
|
create_function_definition = gg_client.create_function_definition(
|
|
InitialVersion=initial_function_version,
|
|
Name='function_def_' + Step.get_random_string(5),
|
|
)
|
|
args[KEY_FUNCTION_DEF_VER_ARN] = create_function_definition[cls.KEY_LATEST_VER_ARN]
|
|
logger.debug('The function definition was created.')
|
|
|
|
@classmethod
|
|
def clean_up(cls, args):
|
|
logger = args.get(KEY_LOGGER)
|
|
greengrasssdk_zip_path = os.path.join(cls.TMP_DIR, cls.GG_PY_SDK_ZIP)
|
|
greengrasssdk_dir_path = os.path.join(cls.TMP_DIR, cls.GG_PY_SDK_DIR)
|
|
remove_file_or_dir(greengrasssdk_zip_path)
|
|
logger.debug("Removing {}.".format(greengrasssdk_zip_path))
|
|
remove_file_or_dir(greengrasssdk_dir_path)
|
|
logger.debug("Removing {}.".format(greengrasssdk_dir_path))
|
|
|
|
|
|
class GroupDefinitionBootstrap(Step):
|
|
KEY_ID = 'Id'
|
|
KEY_LATEST_VER_ARN = 'LatestVersionArn'
|
|
KEY_GROUP_CERT_ARN = 'GroupCertificateAuthorityArn'
|
|
KEY_LATEST_VER_ID = 'LatestVersion'
|
|
|
|
def __init__(self, args):
|
|
super(GroupDefinitionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the group definition..."
|
|
logger.verbose(msg)
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
GroupDefinitionBootstrap.prepare_group(self._args, session)
|
|
logger.debug("The group definition was configured.")
|
|
return self._args
|
|
|
|
@classmethod
|
|
def prepare_group(cls, args, session):
|
|
gg_client = session.client(GREENGRASS)
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug('Creating the group definition...')
|
|
group_name = args[KEY_GROUP_NAME]
|
|
|
|
initial_group_definition_version = {
|
|
'CoreDefinitionVersionArn': args[KEY_CORE_DEF_VER_ARN],
|
|
'LoggerDefinitionVersionArn': args[KEY_LOGGER_VER_ARN],
|
|
}
|
|
# The Function/Subscription Definition are created as requested
|
|
if args[KEY_HAS_HELLO_WORLD_LAMBDA]:
|
|
initial_group_definition_version.update([
|
|
('FunctionDefinitionVersionArn', args[KEY_FUNCTION_DEF_VER_ARN]),
|
|
('SubscriptionDefinitionVersionArn', args[KEY_SUBSCRIPTION_VER_ARN]),
|
|
])
|
|
|
|
# Create the Group Definition with version
|
|
create_group_definition = gg_client.create_group(
|
|
InitialVersion=initial_group_definition_version,
|
|
Name=group_name,
|
|
)
|
|
|
|
logger.debug('The group definition was created.')
|
|
|
|
args[KEY_GROUP_ID] = create_group_definition[cls.KEY_ID]
|
|
args[KEY_GROUP_DEF_VER_ARN] = create_group_definition[cls.KEY_LATEST_VER_ARN]
|
|
args[KEY_GROUP_DEF_VER_ID] = create_group_definition[cls.KEY_LATEST_VER_ID]
|
|
|
|
|
|
class SubscriptionDefinitionBootstrap(Step):
|
|
KEY_LATEST_VER_ARN = 'LatestVersionArn'
|
|
|
|
def __init__(self, args):
|
|
super(SubscriptionDefinitionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
if self._args[KEY_HAS_HELLO_WORLD_LAMBDA]: # only do the following if a hello-world lambda is requested
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the subscription definition..."
|
|
logger.verbose(msg)
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
SubscriptionDefinitionBootstrap.prepare_subscription_definition(self._args, session)
|
|
logger.debug("The subscription definition was configured.")
|
|
return self._args
|
|
|
|
@classmethod
|
|
def prepare_subscription_definition(cls, args, session):
|
|
gg_client = session.client(GREENGRASS)
|
|
logger = args.get(KEY_LOGGER)
|
|
|
|
logger.debug("Creating the subscription definition...")
|
|
create_subscription_definition = gg_client.create_subscription_definition(
|
|
InitialVersion={
|
|
'Subscriptions': [
|
|
{
|
|
'Id': 'Subscription_helloworld_to_cloud_' + Step.get_random_string(5),
|
|
'Source': args[KEY_HELLO_WORLD_LAMBDA_VERSIONED_ARN],
|
|
'Subject': 'hello/world',
|
|
'Target': 'cloud'
|
|
},
|
|
]
|
|
},
|
|
Name='Subscription_definition_' + Step.get_random_string(5),
|
|
)
|
|
logger.debug("The subscription definition was created.")
|
|
|
|
args[KEY_SUBSCRIPTION_VER_ARN] = create_subscription_definition[cls.KEY_LATEST_VER_ARN]
|
|
|
|
|
|
class LoggerDefinitionBootstrap(Step):
|
|
KEY_NAME = 'Name'
|
|
KEY_LATEST_VER_ARN = 'LatestVersionArn'
|
|
|
|
def __init__(self, args):
|
|
super(LoggerDefinitionBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the logger definition..."
|
|
logger.verbose(msg)
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
LoggerDefinitionBootstrap.prepare_logger_definition(self._args, session)
|
|
logger.debug("The logger definition was configured.")
|
|
return self._args
|
|
|
|
@classmethod
|
|
def prepare_logger_definition(cls, args, session):
|
|
gg_client = session.client(GREENGRASS)
|
|
logger = args.get(KEY_LOGGER)
|
|
|
|
logger.debug("Creating the logger definition...")
|
|
create_logger_definition = gg_client.create_logger_definition(
|
|
InitialVersion={
|
|
'Loggers': [
|
|
{
|
|
'Component': 'GreengrassSystem',
|
|
'Id': 'Logger_definition_to_greengrass_system_' + Step.get_random_string(5),
|
|
'Level': 'INFO',
|
|
'Space': 1280,
|
|
'Type': 'FileSystem',
|
|
},
|
|
{
|
|
'Component': 'Lambda',
|
|
'Id': 'Logger_definition_to_lambda_' + Step.get_random_string(5),
|
|
'Level': 'INFO',
|
|
'Space': 1280,
|
|
'Type': 'FileSystem',
|
|
},
|
|
]
|
|
},
|
|
Name='Logger_definition_' + Step.get_random_string(5),
|
|
)
|
|
logger.debug("The logger definition was created.")
|
|
|
|
args[KEY_LOGGER_VER_ARN] = create_logger_definition[cls.KEY_LATEST_VER_ARN]
|
|
args[KEY_LOGGER_NAME] = create_logger_definition[cls.KEY_NAME]
|
|
|
|
|
|
class GreengrassCoreKickoff(Step):
|
|
def __init__(self, args):
|
|
super(GreengrassCoreKickoff, self).__init__(args)
|
|
# step sequence matters
|
|
# Removing CoreSoftwareBootstrap to put it higher in install
|
|
self._steps = [
|
|
CertKeyBootstrap,
|
|
ConfigJsonBootstrap,
|
|
]
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
|
|
logger.info("Preparing the Greengrass core software...")
|
|
for step in self._steps:
|
|
self._args = step(self._args).execute()
|
|
|
|
ggc_daemon_dir = os.path.abspath(os.path.join(self._args[KEY_GGC_ROOT_PATH], "/greengrass/ggc/core"))
|
|
with ChangeDirectory(ggc_daemon_dir):
|
|
cmd_start_ggc = "./greengrassd start"
|
|
logger.debug("Running command: {}.".format(cmd_start_ggc))
|
|
Step.run_linux_cmd_raise_on_failure(cmd_string=cmd_start_ggc,
|
|
err_code=StepError.ERR_GG_START,
|
|
err_msg="Not able to start GGC")
|
|
|
|
logger.info("The Greengrass core software is running.\n")
|
|
|
|
return self._args
|
|
|
|
|
|
class ConfigJsonBootstrap(Step):
|
|
CONFIG_DIR = "/greengrass/config/"
|
|
CONFIG_JSON_FILE = "config.json"
|
|
IOT_ENDPOINT_TYPE_ATS = "iot:Data-ATS"
|
|
|
|
KEY_ENDPOINT_ADDR = "endpointAddress"
|
|
KEY_CORE_THING = "coreThing"
|
|
KEY_RUNTIME = "runtime"
|
|
KEY_MANAGED_RESPAWN = "managedRespawn"
|
|
KEY_CRYPTO = "crypto"
|
|
KEY_CA_PATH = "caPath"
|
|
KEY_CERT_PATH = "certPath"
|
|
KEY_PRIV_KEY_PATH = "keyPath"
|
|
KEY_THING_ARN = "thingArn"
|
|
KEY_IOT_HOST = "iotHost"
|
|
KEY_GG_HOST = "ggHost"
|
|
KEY_KEEP_ALIVE = "keepAlive"
|
|
KEY_CGROUP = "cgroup"
|
|
KEY_USE_SYSTEMD = "UseSystemd"
|
|
KEY_PRINCIPALS = "principals"
|
|
KEY_SECRETS_MANAGER = "SecretsManager"
|
|
KEY_IOT_CERTIFICATE = "IoTCertificate"
|
|
KEY_CERTIFICATE_PATH = "certificatePath"
|
|
KEY_PRIVATE_KEY_PATH = "privateKeyPath"
|
|
|
|
def __init__(self, args):
|
|
super(ConfigJsonBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
|
|
logger.debug("Bootstrapping config.json...")
|
|
config_json = ConfigJsonBootstrap.construct_config_json(self._args, session)
|
|
ConfigJsonBootstrap.persist_config_json(self._args, config_json)
|
|
|
|
return self._args
|
|
|
|
@classmethod
|
|
def construct_config_json(cls, args, session):
|
|
iot_client = session.client(IOT)
|
|
describe_endpoint_resp = iot_client.describe_endpoint(endpointType=cls.IOT_ENDPOINT_TYPE_ATS)
|
|
args[KEY_IOT_DATA_ENDPOINT] = describe_endpoint_resp[cls.KEY_ENDPOINT_ADDR]
|
|
|
|
return {
|
|
cls.KEY_CORE_THING: ConfigJsonBootstrap.construct_core_thing_section(args),
|
|
cls.KEY_RUNTIME: ConfigJsonBootstrap.construct_runtime_section(args),
|
|
cls.KEY_MANAGED_RESPAWN: False,
|
|
cls.KEY_CRYPTO: ConfigJsonBootstrap.construct_crypto_section(args),
|
|
}
|
|
|
|
@classmethod
|
|
def construct_core_thing_section(cls, args):
|
|
return {
|
|
cls.KEY_CA_PATH: args[KEY_ROOT_CA_FILE_LOCATION],
|
|
cls.KEY_CERT_PATH: args[KEY_CORE_CERT_FILE_LOCATION],
|
|
cls.KEY_PRIV_KEY_PATH: args[KEY_CORE_PRIV_KEY_FILE_LOCATION],
|
|
cls.KEY_THING_ARN: args[KEY_CORE_THING_ARN],
|
|
cls.KEY_IOT_HOST: args[KEY_IOT_DATA_ENDPOINT],
|
|
cls.KEY_GG_HOST: get_gg_ats_data_endpoint(args[KEY_AWS_REGION]),
|
|
cls.KEY_KEEP_ALIVE: DEFAULT_GGC_MQTT_KEEP_ALIVE,
|
|
}
|
|
|
|
@classmethod
|
|
def construct_runtime_section(cls, args):
|
|
return {
|
|
cls.KEY_CGROUP: {
|
|
cls.KEY_USE_SYSTEMD: args[KEY_USE_SYSTEMD],
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def construct_crypto_section(cls, args):
|
|
return {
|
|
cls.KEY_CA_PATH: "file://{}".format(args[KEY_ROOT_CA_FILE_LOCATION]),
|
|
cls.KEY_PRINCIPALS: {
|
|
cls.KEY_SECRETS_MANAGER: {
|
|
cls.KEY_PRIVATE_KEY_PATH: "file://{}".format(args[KEY_CORE_PRIV_KEY_FILE_LOCATION]),
|
|
},
|
|
cls.KEY_IOT_CERTIFICATE: {
|
|
cls.KEY_CERTIFICATE_PATH: "file://{}".format(args[KEY_CORE_CERT_FILE_LOCATION]),
|
|
cls.KEY_PRIVATE_KEY_PATH: "file://{}".format(args[KEY_CORE_PRIV_KEY_FILE_LOCATION]),
|
|
}
|
|
},
|
|
}
|
|
|
|
@classmethod
|
|
def persist_config_json(cls, args, config_json):
|
|
config_dir_full_path = os.path.abspath(os.path.join(args[KEY_GGC_ROOT_PATH], cls.CONFIG_DIR))
|
|
if not os.path.exists(config_dir_full_path):
|
|
os.makedirs(config_dir_full_path)
|
|
|
|
with ChangeDirectory(config_dir_full_path):
|
|
# owner read/write, group and others read-only
|
|
with os.fdopen(os.open(cls.CONFIG_JSON_FILE, os.O_CREAT | os.O_WRONLY, 0o644), 'w') as cjf:
|
|
json.dump(config_json, cjf, indent=4)
|
|
|
|
|
|
class CertKeyBootstrap(Step):
|
|
CERT_KEY_DIR = "/greengrass/certs/"
|
|
CERT_PEM_FILE_FORMAT = "{}.cert.pem"
|
|
PRIV_KEY_FILE_FORMAT = "{}.private.key"
|
|
ROOT_CA_FILE = "root.ca.pem"
|
|
|
|
def __init__(self, args):
|
|
super(CertKeyBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
logger.debug("Persisting the core certificate and key...")
|
|
|
|
CertKeyBootstrap.persist_cert_key(self._args)
|
|
|
|
return self._args
|
|
|
|
@classmethod
|
|
def persist_cert_key(cls, args):
|
|
cert_key_full_dir_path = os.path.abspath(os.path.join(args[KEY_GGC_ROOT_PATH], cls.CERT_KEY_DIR))
|
|
if not os.path.exists(cert_key_full_dir_path):
|
|
os.makedirs(cert_key_full_dir_path)
|
|
|
|
with ChangeDirectory(cert_key_full_dir_path):
|
|
file_prefix = args[KEY_CORE_CERT_ID][:10]
|
|
cert_file = cls.CERT_PEM_FILE_FORMAT.format(file_prefix)
|
|
key_file = cls.PRIV_KEY_FILE_FORMAT.format(file_prefix)
|
|
|
|
# owner read/write, group and others read-only
|
|
with os.fdopen(os.open(cert_file, os.O_CREAT | os.O_WRONLY, 0o644), 'w') as cf:
|
|
cf.write(args[KEY_CORE_CERT_PEM])
|
|
args[KEY_CORE_CERT_FILE_LOCATION] = os.path.join(cert_key_full_dir_path, cert_file)
|
|
del args[KEY_CORE_CERT_PEM]
|
|
|
|
# owner read/write, group and others no permission, as this is **private key**
|
|
with os.fdopen(os.open(key_file, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as kf:
|
|
kf.write(args[KEY_CORE_PRIV_KEY])
|
|
args[KEY_CORE_PRIV_KEY_FILE_LOCATION] = os.path.join(cert_key_full_dir_path, key_file)
|
|
del args[KEY_CORE_PRIV_KEY]
|
|
|
|
# owner read/write, group and others read-only
|
|
urlretrieve(url=ATS_ROOT_CA_RSA_2048_REMOTE_LOATION, filename=cls.ROOT_CA_FILE)
|
|
os.chmod(cls.ROOT_CA_FILE, 0o644)
|
|
args[KEY_ROOT_CA_FILE_LOCATION] = os.path.join(cert_key_full_dir_path, cls.ROOT_CA_FILE)
|
|
|
|
|
|
class CoreSoftwareBootstrap(Step):
|
|
CORE_SOFTWARE_FILE = "greengrass.tar.gz"
|
|
OPEN_WRT = "openwrt"
|
|
ARCHITECTURE_OVERRIDE = {
|
|
PLATFORM_X86_64: "x86-64",
|
|
}
|
|
|
|
def __init__(self, args):
|
|
super(CoreSoftwareBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
logger = self._args.get(KEY_LOGGER)
|
|
msg = "Configuring the Greengrass core software..."
|
|
logger.verbose(msg)
|
|
|
|
with yaspin().shark:
|
|
CoreSoftwareBootstrap.download_and_unpack(self._args)
|
|
|
|
return self._args
|
|
|
|
@classmethod
|
|
def download_and_unpack(cls, args):
|
|
logger = args.get(KEY_LOGGER)
|
|
try:
|
|
software_url = cls.find_ggc_software_remote_location(args)
|
|
logger.debug("Downloading the GGC software package from {}.".format(software_url))
|
|
urlretrieve(url=software_url, filename=cls.CORE_SOFTWARE_FILE)
|
|
|
|
ggc_full_root_path = os.path.abspath(args[KEY_GGC_ROOT_PATH])
|
|
logger.debug("Unpacking the GGC software package to {}.".format(ggc_full_root_path))
|
|
|
|
# TarFile does not provide an easy context manager that manages fd closure
|
|
tf = None
|
|
try:
|
|
tf = tarfile.open(cls.CORE_SOFTWARE_FILE, "r:gz")
|
|
tf.extractall(path=ggc_full_root_path)
|
|
finally:
|
|
if tf is not None:
|
|
tf.close()
|
|
finally:
|
|
remove_file_or_dir(cls.CORE_SOFTWARE_FILE)
|
|
logger.debug("Remove {}.".format(cls.CORE_SOFTWARE_FILE))
|
|
|
|
@classmethod
|
|
def find_ggc_software_remote_location(cls, args):
|
|
distribution = "linux"
|
|
if cls.OPEN_WRT in repr(distro.linux_distribution()).lower():
|
|
distribution = cls.OPEN_WRT
|
|
architecture = cls.ARCHITECTURE_OVERRIDE.get(args[KEY_ARCHITECTURE], args[KEY_ARCHITECTURE])
|
|
|
|
return GGC_SOFTWARE_REMOTE_LOCATION_FORMAT.format(args[KEY_GGC_VERSION], distribution, architecture)
|
|
|
|
|
|
class PostBootstrap(Step):
|
|
KEY_DEPLOYMENT_ID = 'DeploymentId'
|
|
KEY_DEPLOYMENT_ARN = 'DeploymentArn'
|
|
KEY_DEPLOYMENT_STATUS = 'DeploymentStatus'
|
|
KEY_ERROR_MESSAGE = 'ErrorMessage'
|
|
|
|
def __init__(self, args):
|
|
super(PostBootstrap, self).__init__(args)
|
|
|
|
def execute(self):
|
|
try:
|
|
session = self._args[KEY_BOTO_SESSION]
|
|
PostBootstrap.try_handle_deployment(self._args, session)
|
|
PostBootstrap.display_bootstrap_result(self._args)
|
|
finally:
|
|
remove_config_file(self._args)
|
|
return self._args
|
|
|
|
@classmethod
|
|
def try_handle_deployment(cls, args, session):
|
|
if args[KEY_HAS_HELLO_WORLD_LAMBDA] is True:
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.info("Configuring the group deployment...")
|
|
|
|
PostBootstrap.prepare_deployment(args, session)
|
|
|
|
spinner = yaspin().shark
|
|
spinner.start()
|
|
try:
|
|
PostBootstrap.wait_until_deployment_done(args, session)
|
|
except:
|
|
spinner.stop()
|
|
raise
|
|
|
|
spinner.stop()
|
|
logger.info("The group deployment is complete.\n")
|
|
|
|
@classmethod
|
|
def prepare_deployment(cls, args, session):
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Creating a deployment for the group...")
|
|
gg_client = session.client(GREENGRASS)
|
|
|
|
# To create a deployment for HelloWorld Function
|
|
create_deployment_response = gg_client.create_deployment(
|
|
DeploymentType='NewDeployment',
|
|
GroupId=args[KEY_GROUP_ID],
|
|
GroupVersionId=args[KEY_GROUP_DEF_VER_ID],
|
|
)
|
|
args[KEY_DEPLOYMENT_ARN] = create_deployment_response[cls.KEY_DEPLOYMENT_ARN]
|
|
args[KEY_DEPLOYMENT_ID] = create_deployment_response[cls.KEY_DEPLOYMENT_ID]
|
|
|
|
@classmethod
|
|
def wait_until_deployment_done(cls, args, session):
|
|
gg_client = session.client(GREENGRASS)
|
|
logger = args.get(KEY_LOGGER)
|
|
logger.debug("Getting the deployment status...")
|
|
try:
|
|
deployment_status_response = retry_template(cls.check_deployment_status, args, gg_client)
|
|
final_deployment_result = deployment_status_response[cls.KEY_DEPLOYMENT_STATUS]
|
|
logger.debug("The status of group deployment is %s" % final_deployment_result)
|
|
if final_deployment_result == "Failure":
|
|
raise StepError(code=StepError.ERR_POST_BOOTSTRAP, message="Group deployment has failed. Detail: %s"
|
|
% deployment_status_response[cls.KEY_ERROR_MESSAGE])
|
|
except StepError:
|
|
raise
|
|
except Exception:
|
|
msg = "Group deployment has failed. Exceeded the upper bound time for deployment."
|
|
logger.debug(msg)
|
|
raise StepError(code=StepError.ERR_POST_BOOTSTRAP, message=msg)
|
|
|
|
@classmethod
|
|
def check_deployment_status(cls, args, gg_client):
|
|
logger = args.get(KEY_LOGGER)
|
|
deployment_status_response = gg_client.get_deployment_status(
|
|
DeploymentId=args[KEY_DEPLOYMENT_ID],
|
|
GroupId=args[KEY_GROUP_ID]
|
|
)
|
|
deployment_status_detail = deployment_status_response[cls.KEY_DEPLOYMENT_STATUS]
|
|
if deployment_status_detail != "Success" and deployment_status_detail != "Failure":
|
|
logger.debug('Deployment is {}. Querying the deployment status again.'.format(deployment_status_detail))
|
|
raise Exception
|
|
return deployment_status_response
|
|
|
|
@staticmethod
|
|
def display_bootstrap_result(args):
|
|
print("\n=======================================================================================\n")
|
|
|
|
print("Your device is running the Greengrass core software. ")
|
|
if args[KEY_HAS_HELLO_WORLD_LAMBDA]:
|
|
print("Your Greengrass group and Hello World Lambda function were deployed to the core device.\n")
|
|
else:
|
|
print("Your Greengrass group was created.\n")
|
|
print("\nSetup information:\n")
|
|
print("Device info: " + str(args[KEY_DEVICE_PLATFORM]))
|
|
print("Greengrass core software location: " + str(args[KEY_GGC_ROOT_PATH]))
|
|
print("Installed Greengrass core software version: " + str(args[KEY_GGC_VERSION]))
|
|
print("Greengrass core: " + str(args[KEY_CORE_THING_ARN]))
|
|
print("Greengrass core IoT certificate: " + str(args[KEY_CORE_CERT_ARN]))
|
|
print("Greengrass core IoT certificate location: " + str(args[KEY_CORE_CERT_FILE_LOCATION]))
|
|
print("Greengrass core IoT key location: " + str(args[KEY_CORE_PRIV_KEY_FILE_LOCATION]))
|
|
print("Deployed Greengrass group name: " + str(args[KEY_GROUP_NAME]))
|
|
print("Deployed Greengrass group ID: " + str(args[KEY_GROUP_ID]))
|
|
print("Deployed Greengrass group version: " + str(args[KEY_GROUP_DEF_VER_ARN]))
|
|
print("Greengrass service role: " + str(args[KEY_GG_ACCOUNT_SERVICE_ROLE]))
|
|
print("GreengrassDeviceSetup log location: " + args[KEY_LOG_FILE])
|
|
|
|
if args[KEY_HAS_HELLO_WORLD_LAMBDA]:
|
|
print("Deployed the HelloWorld Lambda function: " + args.get(KEY_HELLO_WORLD_LAMBDA_VERSIONED_ARN, "None"))
|
|
print("Hello-world subscriber topic: hello/world")
|
|
print("\nYou can now use the AWS IoT Console to subscribe \n"
|
|
"to the 'hello/world' topic to receive messages published from your \n"
|
|
"Greengrass core.")
|
|
else:
|
|
print("\nYou can now use AWS IoT Console to manage your Greengrass group.\n")
|
|
|
|
print("\n=======================================================================================\n")
|
|
|
|
|
|
def remove_config_file(args):
|
|
logger = args.get(KEY_LOGGER)
|
|
gg_device_setup_config_info_file = args[KEY_CONFIG_INFO_FILE_PATH]
|
|
remove_file_or_dir(gg_device_setup_config_info_file)
|
|
logger.debug("Remove GreengrassDeviceSetup.config.info")
|
|
|
|
|
|
# util methods/classes
|
|
def recursive_sort(data):
|
|
if isinstance(data, list):
|
|
for item in data:
|
|
recursive_sort(item)
|
|
data.sort(key=lambda x: str(x))
|
|
if isinstance(data, dict):
|
|
for value in data.values():
|
|
recursive_sort(value)
|
|
|
|
|
|
def remove_file_or_dir(file_or_dir_path):
|
|
if not os.path.exists(file_or_dir_path):
|
|
return
|
|
if not os.path.isdir(file_or_dir_path):
|
|
os.remove(file_or_dir_path)
|
|
else:
|
|
os.rmdir(file_or_dir_path)
|
|
|
|
|
|
def build_config_info_file(args):
|
|
config_info = {}
|
|
for key, value in args.items():
|
|
# We persist the user input required by GreengrassDeviceSetup but skip the sensitive ones,
|
|
# which we request another round of user input after reboot.
|
|
if key in ArgsCollection.ARG_VALIDATORS.keys() and \
|
|
key in ArgsCollection.REQUIRED_ARGS_IN_ORDER:
|
|
config_info[key] = value
|
|
config_info_file = args.get(KEY_CONFIG_INFO_FILE_PATH)
|
|
with open(config_info_file, "w") as f:
|
|
json.dump(config_info, f)
|
|
|
|
|
|
def get_gg_ats_data_endpoint(region):
|
|
if region == "cn-north-1": # BJS is so special
|
|
return "greengrass.ats.iot.cn-north-1.amazonaws.com.cn"
|
|
return "greengrass-ats.iot.{}.amazonaws.com".format(region)
|
|
|
|
|
|
# wait 2^n * 1000 milliseconds between each retry, up to CONFIG_DEPLOYMENT_TIMEOUT milliseconds
|
|
@retry(wait_exponential_multiplier=1000, wait_exponential_max=16000, stop_max_delay=CONFIG_DEPLOYMENT_TIMEOUT*1000)
|
|
def retry_template(func, *args):
|
|
return func(*args)
|
|
|
|
|
|
# util classes
|
|
class StepError(Exception):
|
|
# Available error codes
|
|
ERR_ENV_PREVALIDATE = 1
|
|
ERR_GG_ENV_BOOTSTRAP = 2
|
|
ERR_GG_CLOUD_BOOTSTRAP = 3
|
|
ERR_GG_START = 4
|
|
ERR_POST_BOOTSTRAP = 5
|
|
ERR_INVALID_CREDENTIALS = 6
|
|
ERR_MOUNT_CGROUP = 7
|
|
ERR_ARG_COLLECTION = 8
|
|
ERR_UNKNOWN = 255
|
|
|
|
EXCP_MSG_FMT = "Code {}, Message: {}"
|
|
|
|
def __init__(self, code, message):
|
|
super(Exception, self).__init__(StepError.EXCP_MSG_FMT.format(code, message))
|
|
self._code = code
|
|
self._msg = message
|
|
|
|
@property
|
|
def code(self):
|
|
return self._code
|
|
|
|
@property
|
|
def msg(self):
|
|
return self._msg
|
|
|
|
|
|
# context manager that guarantees switching back to original directory when outside the context
|
|
class ChangeDirectory:
|
|
|
|
def __init__(self, new_path):
|
|
self.new_path = os.path.expanduser(new_path)
|
|
|
|
def __enter__(self):
|
|
self.saved_path = os.getcwd()
|
|
os.chdir(self.new_path)
|
|
|
|
def __exit__(self, etype, value, traceback):
|
|
os.chdir(self.saved_path)
|
|
|
|
|
|
# entry point
|
|
if __name__ == "__main__":
|
|
main()
|
|
|
|
EOF
|
|
)
|
|
|
|
# show spinning wheel
|
|
spin()
|
|
{
|
|
sp='/-\|'
|
|
while true; do
|
|
printf '\b%.1s\b' "$sp"
|
|
sp=${sp#?}${sp%???}
|
|
sleep $SLEEP_TIME
|
|
done
|
|
}
|
|
|
|
# Start the spinner
|
|
start_spinner(){
|
|
# sleep time in Openwrt must be integer
|
|
if [ "$PKG_TOOL" = "$OPKG" ]; then
|
|
SLEEP_TIME=1
|
|
fi
|
|
spin &
|
|
SPIN_PID=$!
|
|
trap 'kill -9 $SPIN_PID' $(seq 1 15)
|
|
}
|
|
|
|
# Stop the spinner
|
|
stop_spinner(){
|
|
if [ ! "$SPIN_PID" = "spin_pid" ]; then
|
|
printf "\b"
|
|
|
|
kill -9 $SPIN_PID
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -eq 0 ]; then
|
|
SPIN_PID="spin_pid"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Functions for clean-ups
|
|
clean_up_pip()
|
|
{
|
|
if [ -d "$PIP_INSTALL_PATH" ]; then
|
|
|
|
LOG_MSG="$ECHO_HEADER Cleaning up the dedicated $PIP for greengrass device setup..."
|
|
log
|
|
|
|
LOG_MSG="$ECHO_HEADER Cleaning up $GET_PIP_PY_DOWNLOAD_DIR/$GET_PIP_PY ..."
|
|
log
|
|
$RM -f "$GET_PIP_PY_DOWNLOAD_DIR/$GET_PIP_PY" >> $GG_DEVICE_SETUP_SHELL_LOG_FILE 2>&1
|
|
|
|
LOG_MSG="$ECHO_HEADER Cleaning up $PIP_INSTALL_PATH ..."
|
|
log
|
|
$RM -rf $PIP_INSTALL_PATH >> $GG_DEVICE_SETUP_SHELL_LOG_FILE 2>&1
|
|
|
|
fi
|
|
}
|
|
|
|
clean_up_all()
|
|
{
|
|
clean_up_pip
|
|
}
|
|
|
|
# Functions for clean exit
|
|
clean_exit()
|
|
{
|
|
stop_spinner
|
|
clean_up_all
|
|
exit $CMD_EXIT_CODE
|
|
}
|
|
|
|
# Functions to emit message to log file
|
|
log()
|
|
{
|
|
echo "$LOG_MSG" >> $GG_DEVICE_SETUP_SHELL_LOG_FILE
|
|
}
|
|
|
|
print()
|
|
{
|
|
echo "$LOG_MSG"
|
|
}
|
|
|
|
print_and_log()
|
|
{
|
|
print
|
|
log
|
|
}
|
|
|
|
print_help_info()
|
|
{
|
|
echo "Usage:"
|
|
echo "sudo -E ./gg-device-setup-latest.sh"
|
|
echo " [ -h | --help ]"
|
|
echo " [ -v | --version ]"
|
|
echo " { bootstrap-greengrass-interactive | bootstrap-greengrass }"
|
|
echo ""
|
|
echo ""
|
|
echo "--help"
|
|
echo "Prints this help info. Can also be run as ./gg-device-setup-latest.sh --help"
|
|
echo ""
|
|
echo "--version"
|
|
echo "Prints the version of GreengrassDeviceSetup. Can also be run as ./gg-device-setup-latest.sh --version"
|
|
echo ""
|
|
echo "bootstrap-greengrass-interactive"
|
|
echo "Starts bootstrapping the Greengrass core in interactive mode."
|
|
echo ""
|
|
echo "bootstrap-greengrass"
|
|
echo "Starts bootstrapping the Greengrass core in CLI mode."
|
|
echo "To see more optional arguments, run as sudo -E ./gg-device-setup-latest.sh bootstrap-greengrass -h"
|
|
echo ""
|
|
echo "--verbose"
|
|
echo "Makes GreengrassDeviceSetup verbose during the operation. e.g. sudo -E ./gg-device-setup-latest.sh bootstrap-greengrass-interactive --verbose"
|
|
}
|
|
|
|
# Functions to parse command line params
|
|
parse_cmdline()
|
|
{
|
|
# Block wrong param
|
|
if [ "$#" -eq 0 ]; then
|
|
print_help_info
|
|
CMD_EXIT_CODE=$ERR_PARAM
|
|
exit $CMD_EXIT_CODE
|
|
fi
|
|
|
|
while [ "$#" -gt 0 ]
|
|
do
|
|
case "$1" in
|
|
-v | --version)
|
|
echo "v$GG_DEVICE_SETUP_VERSION"
|
|
CMD_EXIT_CODE=$NO_ERR
|
|
exit
|
|
;;
|
|
bootstrap-greengrass-interactive)
|
|
break
|
|
;;
|
|
bootstrap-greengrass)
|
|
break
|
|
;;
|
|
-h | --help)
|
|
print_help_info
|
|
CMD_EXIT_CODE=$NO_ERR
|
|
exit $CMD_EXIT_CODE
|
|
;;
|
|
*)
|
|
print_help_info
|
|
CMD_EXIT_CODE=$ERR_PARAM
|
|
exit $CMD_EXIT_CODE
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
# Functions to validate root permissions
|
|
validate_run_as_root()
|
|
{
|
|
if [ ! "$($ID -u)" = 0 ]; then
|
|
echo "The script needs to be run using sudo"
|
|
CMD_EXIT_CODE=$ERR_NO_ROOT
|
|
exit $CMD_EXIT_CODE
|
|
fi
|
|
}
|
|
|
|
# Functions to prepare /tmp
|
|
prepare_root_tmp_dir()
|
|
{
|
|
$MKDIR -p $TMP_DIR > /dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
echo "Not able to create directory: $TMP_DIR"
|
|
CMD_EXIT_CODE=$ERR_NO_TMP_DIR
|
|
exit $CMD_EXIT_CODE
|
|
fi
|
|
}
|
|
|
|
# Functions to prepare GreengrassDeviceSetup shell log file
|
|
prepare_gg_device_setup_shell_log()
|
|
{
|
|
LINUX_EPOCH=$(${DATE} +"%s")
|
|
GG_DEVICE_SETUP_SHELL_LOG_FILE="$GG_DEVICE_SETUP_SHELL_LOG_PATH/greengrass-device-setup-bootstrap-$LINUX_EPOCH.log"
|
|
|
|
type > "$GG_DEVICE_SETUP_SHELL_LOG_FILE"
|
|
if [ $? -ne 0 ]; then
|
|
echo "Not able to create log file for GreengrassDeviceSetup bootstrap at: $GG_DEVICE_SETUP_SHELL_LOG_FILE"
|
|
CMD_EXIT_CODE=$ERR_LOG_FILE
|
|
clean_exit
|
|
fi
|
|
}
|
|
|
|
# Functions to display banner
|
|
display_banner()
|
|
{
|
|
LOG_MSG="############### Greengrass Device Setup v$GG_DEVICE_SETUP_VERSION ###############"
|
|
print_and_log
|
|
|
|
LOG_MSG="$ECHO_HEADER The Greengrass Device Setup bootstrap log is available at: $GG_DEVICE_SETUP_SHELL_LOG_FILE"
|
|
print_and_log
|
|
}
|
|
|
|
check_if_command_present()
|
|
{
|
|
INPUT_CMD="$1"
|
|
|
|
if ! type "$INPUT_CMD" > /dev/null 2>&1; then
|
|
LOG_MSG="The '$INPUT_CMD' command not found."
|
|
print_and_log
|
|
|
|
CMD_EXIT_CODE=$ERR_PREREQ
|
|
clean_exit
|
|
fi
|
|
}
|
|
|
|
validate_prereq()
|
|
{
|
|
LOG_MSG="$ECHO_HEADER Validating pre-requisites..."
|
|
log
|
|
|
|
check_if_command_present "id"
|
|
check_if_command_present "cat"
|
|
check_if_command_present "cd"
|
|
check_if_command_present "date"
|
|
check_if_command_present "mkdir"
|
|
check_if_command_present "printf"
|
|
check_if_command_present "sleep"
|
|
check_if_command_present "kill"
|
|
check_if_command_present "trap"
|
|
check_if_command_present "seq"
|
|
check_if_command_present "find"
|
|
}
|
|
|
|
|
|
# Functions to identify package tool
|
|
identify_package_tool()
|
|
{
|
|
LOG_MSG="$ECHO_HEADER Identifying package management tool..."
|
|
log
|
|
|
|
if type $APT_GET > /dev/null 2>&1; then
|
|
PKG_TOOL=$APT_GET
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
elif type $APT > /dev/null 2>&1; then
|
|
PKG_TOOL=$APT
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
elif type $YUM > /dev/null 2>&1; then
|
|
PKG_TOOL=$YUM
|
|
elif type $OPKG > /dev/null 2>&1; then
|
|
PKG_TOOL=$OPKG
|
|
fi
|
|
|
|
if [ "$PKG_TOOL" = "@missing@" ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to find any of the supported package management tools: $APT, $APT_GET, $YUM, $OPKG."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PKG_TOOL
|
|
clean_exit
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Using package management tool: $PKG_TOOL..."
|
|
print_and_log
|
|
}
|
|
|
|
# Functions to update package list
|
|
update_package_list_standard()
|
|
{
|
|
$PKG_TOOL update -y >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
}
|
|
|
|
update_package_list_opkg()
|
|
{
|
|
$PKG_TOOL update >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1 # opkg does not require '-y' option
|
|
CMD_EXIT_CODE=$?
|
|
}
|
|
|
|
update_package_list()
|
|
{
|
|
LOG_MSG="$ECHO_HEADER Updating package list..."
|
|
log
|
|
|
|
case $PKG_TOOL in
|
|
"$APT_GET")
|
|
update_package_list_standard
|
|
;;
|
|
"$APT")
|
|
update_package_list_standard
|
|
;;
|
|
"$YUM")
|
|
update_package_list_standard
|
|
;;
|
|
"$OPKG")
|
|
update_package_list_opkg
|
|
;;
|
|
esac
|
|
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to update package list."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_UPDATE_PKG_LIST
|
|
clean_exit
|
|
fi
|
|
}
|
|
|
|
try_update_package_list()
|
|
{
|
|
if [ "$PKG_LIST_UPDATED" -ne 0 ]; then
|
|
update_package_list
|
|
PKG_LIST_UPDATED=0
|
|
fi
|
|
}
|
|
|
|
# Functions to validate python version
|
|
install_python_standard()
|
|
{
|
|
$PKG_TOOL install -y $PYTHON37 >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
}
|
|
|
|
install_python_opkg()
|
|
{
|
|
$PKG_TOOL install $PYTHON3 >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
}
|
|
|
|
reinstall_module_apt_pkg_for_python37()
|
|
{
|
|
if [ "$PKG_TOOL" = "$APT" ] || [ "$PKG_TOOL" = "$APT_GET" ]; then
|
|
|
|
$PKG_TOOL remove -y python3-apt >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to config module - apt_pkg for Python ($PYTHON37)."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PYTHON
|
|
fi
|
|
|
|
if [ $CMD_EXIT_CODE -ne $ERR_PYTHON ]; then
|
|
$PKG_TOOL install -y python3-apt >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to config module - apt_pkg for Python ($PYTHON37)."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PYTHON
|
|
fi
|
|
fi
|
|
|
|
fi
|
|
}
|
|
|
|
install_python37()
|
|
{
|
|
try_update_package_list
|
|
|
|
case $PKG_TOOL in
|
|
"$APT_GET")
|
|
install_python_standard
|
|
;;
|
|
"$APT")
|
|
install_python_standard
|
|
;;
|
|
"$YUM")
|
|
install_python_standard
|
|
;;
|
|
"$OPKG")
|
|
install_python_opkg
|
|
;;
|
|
*)
|
|
LOG_MSG="$ECHO_HEADER Unrecognized package management tool: $PKG_TOOL."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PKG_TOOL
|
|
clean_exit
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
validate_python37()
|
|
{
|
|
LOG_MSG="$ECHO_HEADER Looking for Python3.7..."
|
|
log
|
|
|
|
if ! type $PYTHON37 > /dev/null 2>&1; then
|
|
LOG_MSG="$ECHO_HEADER Python ($PYTHON37) not found. Attempting to install it..."
|
|
print_and_log
|
|
|
|
install_python37
|
|
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to install Python ($PYTHON37)."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PYTHON
|
|
fi
|
|
|
|
if [ $CMD_EXIT_CODE -ne $ERR_PYTHON ]; then
|
|
if ! type $PYTHON37 > /dev/null 2>&1; then
|
|
LOG_MSG="$ECHO_HEADER Python ($PYTHON37) still not found after installation attempt."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PYTHON
|
|
else
|
|
reinstall_module_apt_pkg_for_python37
|
|
fi
|
|
fi
|
|
|
|
fi
|
|
}
|
|
|
|
validate_python_version()
|
|
{
|
|
# Check whether python37 exists. If not, install python 3.7.
|
|
validate_python37
|
|
PYTHON_USED=$PYTHON37
|
|
|
|
if [ $CMD_EXIT_CODE -eq $ERR_PYTHON ]; then
|
|
# Check whether other python exsits. If not, exit with error message.
|
|
LOG_MSG="$ECHO_HEADER Looking for other installed Python..."
|
|
log
|
|
# find all the installed python on the target device, and take the first one in the returned list
|
|
PYTHON_CANDIDATE=$(find / -name python* 2>/dev/null | grep '[2-3].[0-9]$' | sed 1q)
|
|
# get the python version e.g. python3.5 from PYTHON_CANDIDATE - "/usr/bin/python3.5"
|
|
PYTHON_USED=${PYTHON_CANDIDATE##*/}
|
|
if [ -z "$PYTHON_CANDIDATE" ] ; then
|
|
CMD_EXIT_CODE=$ERR_PYTHON
|
|
LOG_MSG="There is no Python available to proceed GreengrassDeviceSetup. Install Python manually to continue."
|
|
print_and_log
|
|
clean_exit
|
|
fi
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Using runtime: $PYTHON_USED..."
|
|
print_and_log
|
|
}
|
|
|
|
# Functions to validate pip version
|
|
install_wget_standard()
|
|
{
|
|
$PKG_TOOL install -y $WGET >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
}
|
|
|
|
install_wget_opkg()
|
|
{
|
|
$PKG_TOOL install $WGET >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1 # opkg install does not require '-y' option
|
|
CMD_EXIT_CODE=$?
|
|
}
|
|
|
|
install_wget()
|
|
{
|
|
try_update_package_list
|
|
|
|
case $PKG_TOOL in
|
|
"$APT_GET")
|
|
install_wget_standard
|
|
;;
|
|
"$APT")
|
|
install_wget_standard
|
|
;;
|
|
"$YUM")
|
|
install_wget_standard
|
|
;;
|
|
"$OPKG")
|
|
install_wget_opkg
|
|
;;
|
|
*)
|
|
LOG_MSG="$ECHO_HEADER Unrecognized package management tool: $PKG_TOOL."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_PKG_TOOL
|
|
clean_exit
|
|
;;
|
|
esac
|
|
}
|
|
|
|
ensure_wget()
|
|
{
|
|
if ! type $WGET > /dev/null 2>&1; then
|
|
LOG_MSG="$ECHO_HEADER $WGET not found. Try installing it..."
|
|
print_and_log
|
|
|
|
install_wget
|
|
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="Not able to install $WGET."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
fi
|
|
fi
|
|
}
|
|
|
|
install_pip_via_get_pip_py()
|
|
{
|
|
ensure_wget
|
|
|
|
LOG_MSG="$ECHO_HEADER Installing a dedicated $PIP for Greengrass Device Setup..."
|
|
print_and_log
|
|
|
|
$WGET $GET_PIP_PY_URL -O "$GET_PIP_PY_DOWNLOAD_DIR/$GET_PIP_PY" >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
case $PKG_TOOL in
|
|
"$APT_GET")
|
|
LOG_MSG="$ECHO_HEADER Not able to download get-pip.py."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
;;
|
|
"$APT")
|
|
LOG_MSG="$ECHO_HEADER Not able to download get-pip.py."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
;;
|
|
"$YUM")
|
|
LOG_MSG="$ECHO_HEADER Not able to download get-pip.py."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
;;
|
|
"$OPKG")
|
|
LOG_MSG="$ECHO_HEADER Not able to download get-pip.py with $PKG_TOOL."
|
|
log
|
|
LOG_MSG="$ECHO_HEADER Will retry after updating certificates and ssl lib..."
|
|
log
|
|
|
|
LOG_MSG="$ECHO_HEADER Installing ca-bundle..."
|
|
log
|
|
|
|
$PKG_TOOL install ca-bundle >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to install required dependencies - ca-bundle for pip installation."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Installing ca-certificates..."
|
|
log
|
|
|
|
$PKG_TOOL install ca-certificates >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to install required dependencies - ca-certificates for pip installation."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Installing libustream-openssl..."
|
|
log
|
|
|
|
$PKG_TOOL install libustream-openssl >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to install required dependencies - libustream-openssl for pip installation."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Now retry downloading get-pip.py..."
|
|
log
|
|
|
|
$WGET $GET_PIP_PY_URL -O "$GET_PIP_PY_DOWNLOAD_DIR/$GET_PIP_PY" >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Still not able to download get-pip.py after error mitigation attempt."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_WGET
|
|
clean_exit
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [ "$PYTHON_USED" = "$PYTHON37" ] && { [ "$PKG_TOOL" = "$APT" ] || [ "$PKG_TOOL" = "$APT_GET" ] ;}; then
|
|
$PKG_TOOL install -y python3-distutils >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to install required dependencies - python3-distutils for pip installation."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_GET_PIP_PY
|
|
clean_exit
|
|
fi
|
|
fi
|
|
|
|
$PYTHON_USED "$GET_PIP_PY_DOWNLOAD_DIR/$GET_PIP_PY" --prefix $PIP_INSTALL_PATH --no-setuptools --no-wheel >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to install pip with get-pip.py."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_GET_PIP_PY
|
|
clean_exit
|
|
fi
|
|
}
|
|
|
|
# Functions to ensure Python dependencies
|
|
move_to_pip_dir()
|
|
{
|
|
MY_PWD="$PWD"
|
|
|
|
LOG_MSG="$ECHO_HEADER Remembering current directory: $MY_PWD"
|
|
log
|
|
|
|
PIP_IMPORT_PATH="$TMP_DIR/greengrass-device-setup-bootstrap-tmp/lib/$PYTHON_USED/site-packages"
|
|
|
|
$CD "$PIP_IMPORT_PATH" >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to change directory to: $PIP_IMPORT_PATH"
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_CD
|
|
clean_exit
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Changed directory to: $PIP_IMPORT_PATH"
|
|
log
|
|
}
|
|
|
|
move_away_from_pip_dir()
|
|
{
|
|
$CD "$MY_PWD" >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
LOG_MSG="$ECHO_HEADER Not able to change directory back to original one: $MY_PWD"
|
|
print_and_log
|
|
CMD_EXIT_CODE=$ERR_CD
|
|
clean_exit
|
|
fi
|
|
|
|
LOG_MSG="$ECHO_HEADER Changed directory back to: $MY_PWD"
|
|
log
|
|
}
|
|
|
|
validate_dependency_and_install_if_missing()
|
|
{
|
|
INPUT_DEPENDENCY="$1"
|
|
OUTPUT_ERR_CODE="$2"
|
|
|
|
LOG_MSG="$ECHO_HEADER Looking for $INPUT_DEPENDENCY..."
|
|
log
|
|
|
|
if ! $PYTHON_USED -m $PIP show "$INPUT_DEPENDENCY" > /dev/null 2>&1; then
|
|
LOG_MSG="$ECHO_HEADER $INPUT_DEPENDENCY not found. Try installing it..."
|
|
log
|
|
|
|
$PYTHON_USED -m $PIP install "$INPUT_DEPENDENCY" >> "$GG_DEVICE_SETUP_SHELL_LOG_FILE" 2>&1
|
|
|
|
CMD_EXIT_CODE=$?
|
|
if [ $CMD_EXIT_CODE -ne 0 ]; then
|
|
move_away_from_pip_dir
|
|
|
|
LOG_MSG="$ECHO_HEADER Not able to install $INPUT_DEPENDENCY."
|
|
print_and_log
|
|
CMD_EXIT_CODE=$OUTPUT_ERR_CODE
|
|
clean_exit
|
|
fi
|
|
fi
|
|
}
|
|
|
|
validate_retrying_installation()
|
|
{
|
|
LOG_MSG="$ECHO_HEADER Looking for retrying..."
|
|
log
|
|
|
|
if ! $PYTHON_USED -m $PIP show $RETRYING > /dev/null 2>&1; then
|
|
LOG_MSG="$ECHO_HEADER $RETRYING not found. Try looking for required dependencyis to install it..."
|
|
log
|
|
|
|
validate_dependency_and_install_if_missing "$SETUPTOOLS" "$ERR_SETUPTOOLS"
|
|
validate_dependency_and_install_if_missing "$WHEEL" "$ERR_WHEEL"
|
|
validate_dependency_and_install_if_missing "$RETRYING" "$ERR_RETRYING"
|
|
fi
|
|
}
|
|
|
|
ensure_python_dependencies()
|
|
{
|
|
move_to_pip_dir
|
|
|
|
LOG_MSG="$ECHO_HEADER Validating and installing required dependencies..."
|
|
print_and_log
|
|
|
|
validate_dependency_and_install_if_missing "$BOTO3" "$ERR_BOTO3"
|
|
validate_dependency_and_install_if_missing "$DISTRO" "$ERR_DISTRO"
|
|
validate_dependency_and_install_if_missing "$CONFIGPARSER" "$ERR_CONFIGPARSER"
|
|
validate_dependency_and_install_if_missing "$YASPIN" "$ERR_YASPIN"
|
|
validate_retrying_installation
|
|
|
|
move_away_from_pip_dir
|
|
|
|
clean_up_pip
|
|
}
|
|
|
|
# Run python script with other command-line params propagated
|
|
run_gg_device_setup_python_core()
|
|
{
|
|
LOG_MSG="$ECHO_HEADER The Greengrass Device Setup configuration is complete. Starting the Greengrass environment setup..."
|
|
print_and_log
|
|
|
|
LOG_MSG="$ECHO_HEADER Forwarding command-line parameters: $*"
|
|
print_and_log
|
|
printf "\n"
|
|
|
|
# At this point, logging switch to python core
|
|
$PYTHON_USED -c "$CODE" "$@"
|
|
|
|
CMD_EXIT_CODE=$?
|
|
clean_exit
|
|
}
|
|
|
|
|
|
main()
|
|
{
|
|
parse_cmdline "$@"
|
|
validate_run_as_root
|
|
prepare_root_tmp_dir
|
|
prepare_gg_device_setup_shell_log
|
|
display_banner
|
|
validate_prereq
|
|
identify_package_tool
|
|
|
|
start_spinner
|
|
validate_python_version
|
|
install_pip_via_get_pip_py
|
|
ensure_python_dependencies
|
|
stop_spinner
|
|
|
|
run_gg_device_setup_python_core "$@"
|
|
}
|
|
|
|
main "$@"
|