module.exports = function()

in source/services/deployments/lib/addDeployment.js [17:392]


module.exports = function (event, context) {

    let _device;
    let _deviceType;
    let _deviceBlueprint;
    let _newSpec = {};
    let _newGreengrassGroupVersion = {};
    let _groupVersion;
    let _deployment;
    let _savedDeployment;
    let _newShadow = {};
    let _certificateArn;

    let _substitutions = {
        THING_NAME: null,
        CORE: null,
        CORE_ARN: null,
        CORE_CERTIFICATE_ARN: null,
        AWS_REGION: null,
        AWS_ACCOUNT: null,
        DATA_BUCKET: null,
        DATA_BUCKET_S3_URL: null,
        IOT_ENDPOINT: null
    };

    // First lets get the device.
    return documentClient.get({
        TableName: process.env.TABLE_DEVICES,
        Key: {
            thingId: event.thingId
        }
    }).promise().then(device => {
        _device = device.Item;

        if (_device === undefined) {
            throw 'Device does not exist in DB';
        }

        return Promise.all([
            documentClient.get({
                TableName: process.env.TABLE_DEVICE_TYPES,
                Key: {
                    id: _device.deviceTypeId
                }
            }).promise().then(result => _deviceType = result.Item),
            documentClient.get({
                TableName: process.env.TABLE_DEVICE_BLUEPRINTS,
                Key: {
                    id: _device.deviceBlueprintId
                }
            }).promise().then(result => _deviceBlueprint = result.Item)
        ]);
    }).then(results => {

        if (_deviceType === undefined || _deviceBlueprint === undefined) {
            throw 'Device Type or Device Blueprint do not exist in DB';
        }

        console.log('Device:', _device);
        console.log('Device Type:', _deviceType);
        console.log('Device Blueprint:', _deviceBlueprint);

        return iot.listThingPrincipals({
            thingName: _device.thingName
        }).promise();

    }).then(principals => {
        principals = principals.principals;

        if (principals.length === 0) {
            throw 'Device does not yet have a certificate. Need to create one first.';
        }

        _certificateArn = principals[0];

        _substitutions.AWS_ACCOUNT = process.env.AWS_ACCOUNT;
        _substitutions.AWS_REGION = process.env.AWS_REGION;
        _substitutions.THING_NAME = _device.thingName;
        _substitutions.CORE = _device.thingName;
        _substitutions.CORE_ARN = _device.thingArn;
        _substitutions.CORE_CERTIFICATE_ARN = _certificateArn;
        _substitutions.DATA_BUCKET = process.env.DATA_BUCKET;
        _substitutions.DATA_BUCKET_S3_URL = `https://${process.env.DATA_BUCKET}.s3.amazonaws.com`;
        _substitutions.IOT_ENDPOINT = process.env.IOT_ENDPOINT;

        // Order is important
        // Merge Device into DeviceType into DeviceBlueprint
        _newSpec = {};
        _newShadow = {};
        if (_device.spec) {
            console.log(`Device Spec: ${JSON.stringify(_device.spec, null, 4)}`);
            _newSpec = mergeSputnikSpecs(_newSpec, _device.spec);
            _newShadow = mergeSputnikShadows(_newShadow, _device.spec.Shadow);
            console.log(`WIP Spec: ${JSON.stringify(_newSpec, null, 4)}`);
        }
        if (_deviceType.spec) {
            console.log(`Device Type Spec: ${JSON.stringify(_deviceType.spec, null, 4)}`);
            _newSpec = mergeSputnikSpecs(_newSpec, _deviceType.spec);
            _newShadow = mergeSputnikShadows(_newShadow, _deviceType.spec.Shadow);
            console.log(`WIP Spec: ${JSON.stringify(_newSpec, null, 4)}`);
        }
        if (_deviceBlueprint.spec) {
            console.log(`Device Blueprint Spec: ${JSON.stringify(_deviceBlueprint.spec, null, 4)}`);
            _newSpec = mergeSputnikSpecs(_newSpec, _deviceBlueprint.spec);
            _newShadow = mergeSputnikShadows(_newShadow, _deviceBlueprint.spec.Shadow);
            console.log(`WIP Spec: ${JSON.stringify(_newSpec, null, 4)}`);
        }

        // console.log('Deal with actions');
        // _newSpec.afterActions = [...(_device.spec.afterActions || []), ...(_deviceType.spec.afterActions || []), ...(_deviceBlueprint.spec.afterActions || [])];

        console.log('Going to substitute in the spec');
        // Construct the spec:
        let strSpec = JSON.stringify(_newSpec);
        let strShadow = JSON.stringify(_newShadow);
        for (var key in _substitutions) {
            // skip loop if the property is from prototype
            if (!_substitutions.hasOwnProperty(key)) continue;

            var value = _substitutions[key];
            for (var prop in value) {
                // skip loop if the property is from prototype
                if (!value.hasOwnProperty(prop)) continue;

                // your code
                let regExp = new RegExp('[' + key + ']', 'gi');
                strSpec = strSpec.split('[' + key + ']').join(value);
                strShadow = strShadow.split('[' + key + ']').join(value);
            }
        }

        // TODO: Replace this with the !GetAtt system ?
        _deviceBlueprint.deviceTypeMappings.forEach(mapping => {
            if (mapping.value[_deviceType.id]) {
                let regExp = new RegExp('[' + mapping.substitute + ']', 'gi');
                strSpec = strSpec.split('[' + mapping.substitute + ']').join(mapping.value[_deviceType.id]);
            }
        });

        _newSpec = JSON.parse(strSpec);

        _newShadow = JSON.parse(strShadow);
        _newSpec.Shadow = _newShadow;

        // // TODO: this eval thing could be a security risk. Need to potentially rethink this.
        // _newSpec.afterActions.forEach(a => {
        //     console.log('Evaluating:', a);
        //     eval(a);
        //     _newSpec = afterAction(_newSpec);
        // });

        console.log(`Spec out: ${JSON.stringify(_newSpec, null, 4)}`);

        if (_deviceType.type === 'GREENGRASS' && _deviceBlueprint.type === 'GREENGRASS') {

            console.log('Device is a Greengrass device:', _device.greengrassGroupId);

            return gg.getGroup({
                GroupId: _device.greengrassGroupId
            }).promise().then(group => {

                if (!group.LatestVersion) {
                    console.log('Group does not have a definition version yet. We will need to create it later down.');
                    return null;
                } else {
                    return gg.getGroupVersion({
                        GroupId: _device.greengrassGroupId,
                        GroupVersionId: group.LatestVersion
                    }).promise();
                }

            }).then(groupDefinitionVersion => {

                _newGreengrassGroupVersion = {};
                _newGreengrassGroupVersion.GroupId = _device.greengrassGroupId;

                return Promise.all([
                    createGreengrassXDefinitionVersion('Core', _newSpec, groupDefinitionVersion).then(c => {
                        if (c) {
                            _newGreengrassGroupVersion.CoreDefinitionVersionArn = c.Arn;
                        }
                        return c;
                    }),
                    createGreengrassXDefinitionVersion('Function', _newSpec, groupDefinitionVersion).then(f => {
                        if (f) {
                            _newGreengrassGroupVersion.FunctionDefinitionVersionArn = f.Arn;
                        }
                        return f;
                    }),
                    createGreengrassXDefinitionVersion('Logger', _newSpec, groupDefinitionVersion).then(l => {
                        if (l) {
                            _newGreengrassGroupVersion.LoggerDefinitionVersionArn = l.Arn;
                        }
                        return l;
                    }),
                    createGreengrassXDefinitionVersion('Resource', _newSpec, groupDefinitionVersion).then(r => {
                        if (r) {
                            _newGreengrassGroupVersion.ResourceDefinitionVersionArn = r.Arn;
                        }
                        return r;
                    }),
                    createGreengrassXDefinitionVersion('Subscription', _newSpec, groupDefinitionVersion).then(s => {
                        if (s) {
                            _newGreengrassGroupVersion.SubscriptionDefinitionVersionArn = s.Arn;
                        }
                        return s;
                    }),
                    createGreengrassXDefinitionVersion('Device', _newSpec, groupDefinitionVersion).then(d => {
                        if (d) {
                            _newGreengrassGroupVersion.DeviceDefinitionVersionArn = d.Arn;
                        }
                        return d;
                    }),
                    createGreengrassXDefinitionVersion('Connector', _newSpec, groupDefinitionVersion).then(d => {
                        if (d) {
                            _newGreengrassGroupVersion.ConnectorDefinitionVersionArn = d.Arn;
                        }
                        return d;
                    })
                ]);

            }).then(results => {
                console.log('results', JSON.stringify(results));
                console.log('newGreengrassGroupVersion', JSON.stringify(_newGreengrassGroupVersion, null, 2));

                return gg.createGroupVersion(_newGreengrassGroupVersion).promise();

            }).then(groupVersion => {
                _groupVersion = groupVersion;

                console.log(`Created group version: ${JSON.stringify(_groupVersion, null, 2)}`);

                console.log('Attach IAM role to group just in case');

                return gg.associateRoleToGroup({
                    RoleArn: process.env.IAM_ROLE_ARN_FOR_GREENGRASS_GROUPS,
                    GroupId: _device.greengrassGroupId
                }).promise();

            }).then(result => {

                console.log('Attach IOT Greengrass policy to group just in case');

                // TODO: this should ideally be in the specs!

                return iot
                    .attachPrincipalPolicy({
                        policyName: process.env.IOT_POLICY_GREENGRASS_CORE,
                        principal: _certificateArn
                    })
                    .promise();

            }).then(result => {

                console.log(`Deploy group:`);

                return gg.createDeployment({
                    GroupId: _groupVersion.Id,
                    DeploymentId: uuid.v4(),
                    DeploymentType: 'NewDeployment',
                    GroupVersionId: _groupVersion.Version
                }).promise();

            }).then(deployment => {
                _deployment = deployment;
                console.log(`Deployed: ${_deployment.DeploymentId}`);

                _savedDeployment = {
                    thingId: _device.thingId,
                    deploymentId: _deployment.DeploymentId,
                    spec: _newSpec,
                    type: 'GREENGRASS',
                    greengrassGroup: {
                        Id: _groupVersion.Id,
                        VersionId: _groupVersion.Version
                    },
                    createdAt: moment()
                        .utc()
                        .format(),
                    updatedAt: moment()
                        .utc()
                        .format()
                };

                return _savedDeployment;
            });

        } else {
            console.log('Device is NOT a greengrass device, or at least not detected as one. OR the deviceBlueprint/deviceType combination is not for a Greengrass device');

            _savedDeployment = {
                thingId: _device.thingId,
                deploymentId: uuid.v4(),
                spec: _newSpec,
                type: _deviceType.type,
                greengrassGroup: {},
                createdAt: moment()
                    .utc()
                    .format(),
                updatedAt: moment()
                    .utc()
                    .format()
            };

            return _savedDeployment;
        }
    }).then(params => {

        const newDeployment = {
            TableName: process.env.TABLE_DEPLOYMENTS,
            Item: _savedDeployment
        };
        return documentClient.put(newDeployment).promise();

    }).then(deployment => {

        const updateParams = {
            TableName: process.env.TABLE_DEVICES,
            Key: {
                thingId: _device.thingId
            },
            UpdateExpression: 'set #ua = :ua, #l = :l',
            ExpressionAttributeNames: {
                '#ua': 'updatedAt',
                '#l': 'lastDeploymentId'
            },
            ExpressionAttributeValues: {
                ':ua': moment()
                    .utc()
                    .format(),
                ':l': _savedDeployment.deploymentId
            }
        };
        return documentClient.update(updateParams).promise();

    }).then(device => {

        if (JSON.stringify(_newShadow) !== '{}') {
            return iot.describeEndpoint().promise().then(endpoint => {
                const iotdata = new AWS.IotData({
                    endpoint: endpoint.endpointAddress
                });
                return iotdata.updateThingShadow({
                    thingName: _device.thingName,
                    payload: JSON.stringify({
                        state: {}
                    })
                }).promise().then(result => {
                    console.log('Updating with nothing, to create in case it doesnt exist');
                    return iotdata.deleteThingShadow({
                        thingName: _device.thingName
                    }).promise().then(result => {
                        return iotdata.updateThingShadow({
                            thingName: _device.thingName,
                            payload: JSON.stringify({
                                state: _newShadow
                            })
                        }).promise();
                    });
                });
            }).then(result => {
                console.log('Updated shadow per spec:', _newShadow);
                return _savedDeployment;
            });
        } else {
            return _savedDeployment;
        }

    }).then(() => {

        console.log(`AND WE ARE DONE !`);
        return _savedDeployment;

    });

};