Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Greengrass Hello World examples #1191

Merged
merged 1 commit into from
Oct 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
exports.GreengrassInterfaceVersion = '1.3';
exports.Lambda = require('./lambda');
exports.IotData = require('./iotdata');
exports.SecretsManager = require('./secretsmanager');
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/

const Buffer = require('buffer').Buffer;

const Lambda = require('./lambda');
const Util = require('./util');
const GreengrassCommon = require('aws-greengrass-common-js');

const envVars = GreengrassCommon.envVars;
const MY_FUNCTION_ARN = envVars.MY_FUNCTION_ARN;
const SHADOW_FUNCTION_ARN = envVars.SHADOW_FUNCTION_ARN;
const ROUTER_FUNCTION_ARN = envVars.ROUTER_FUNCTION_ARN;

class IotData {
constructor() {
this.lambda = new Lambda();
}

getThingShadow(params, callback) {
/*
* Call shadow lambda to obtain current shadow state.
* @param {object} params object contains parameters for the call
* REQUIRED: 'thingName' the name of the thing
*/
const thingName = Util.getRequiredParameter(params, 'thingName');
if (thingName === undefined) {
callback(new Error('"thingName" is a required parameter.'), null);
return;
}

const payload = '';
this._shadowOperation('get', thingName, payload, callback);
}

updateThingShadow(params, callback) {
/*
* Call shadow lambda to update current shadow state.
* @param {object} params object contains parameters for the call
* REQUIRED: 'thingName' the name of the thing
* 'payload' the state information in JSON format
*/
const thingName = Util.getRequiredParameter(params, 'thingName');
if (thingName === undefined) {
callback(new Error('"thingName" is a required parameter.'), null);
return;
}

const payload = Util.getRequiredParameter(params, 'payload');
if (payload === undefined) {
callback(new Error('"payload" is a required parameter.'), null);
return;
}

this._shadowOperation('update', thingName, payload, callback);
}

deleteThingShadow(params, callback) {
/*
* Call shadow lambda to delete the shadow state.
* @param {object} params object contains parameters for the call
* REQUIRED: 'thingName' the name of the thing
*/
const thingName = Util.getRequiredParameter(params, 'thingName');
if (thingName === undefined) {
callback(new Error('"thingName" is a required parameter.'), null);
return;
}

const payload = '';
this._shadowOperation('delete', thingName, payload, callback);
}

publish(params, callback) {
/*
* Publishes state information.
* @param {object} params object contains parameters for the call
* REQUIRED: 'topic' the topic name to be published
* 'payload' the state information in JSON format
*/
const topic = Util.getRequiredParameter(params, 'topic');
if (topic === undefined) {
callback(new Error('"topic" is a required parameter'), null);
return;
}

const payload = Util.getRequiredParameter(params, 'payload');
if (payload === undefined) {
callback(new Error('"payload" is a required parameter'), null);
return;
}

const context = {
custom: {
source: MY_FUNCTION_ARN,
subject: topic,
},
};

const buff = Buffer.from(JSON.stringify(context));
const clientContext = buff.toString('base64');

const invokeParams = {
FunctionName: ROUTER_FUNCTION_ARN,
InvocationType: 'Event',
ClientContext: clientContext,
Payload: payload,
};

console.log(`Publishing message on topic "${topic}" with Payload "${payload}"`);

this.lambda.invoke(invokeParams, (err, data) => {
if (err) {
callback(err, null); // an error occurred
} else {
callback(null, data); // successful response
}
});
}

_shadowOperation(operation, thingName, payload, callback) {
const topic = `$aws/things/${thingName}/shadow/${operation}`;
const context = {
custom: {
subject: topic,
},
};

const clientContext = Buffer.from(JSON.stringify(context)).toString('base64');
const invokeParams = {
FunctionName: SHADOW_FUNCTION_ARN,
ClientContext: clientContext,
Payload: payload,
};

console.log(`Calling shadow service on topic "${topic}" with payload "${payload}"`);
this.lambda.invoke(invokeParams, (err, data) => {
if (err) {
callback(err, null);
} else {
callback(null, data);
}
});
}
}

module.exports = IotData;
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/

const Util = require('./util');
const IPCClient = require('aws-greengrass-ipc-sdk-js');
const GreengrassCommon = require('aws-greengrass-common-js');
const logging = require('aws-greengrass-common-js').logging;

const AUTH_TOKEN = GreengrassCommon.envVars.AUTH_TOKEN;

const logger = new logging.LocalWatchLogger();

class Lambda {
constructor() {
this.ipc = new IPCClient(AUTH_TOKEN);
}

invoke(params, callback) {
const functionName = Util.getRequiredParameter(params, 'FunctionName');
if (functionName === undefined) {
callback(new Error('"FunctionName" is a required parameter'), null);
return;
}

let arnFields;
try {
arnFields = new GreengrassCommon.FunctionArnFields(functionName);
} catch (e) {
callback(new Error(`FunctionName is malformed: ${e}`), null);
return;
}

let invocationType;
if (params.InvocationType === undefined || params.InvocationType === null) {
invocationType = 'RequestResponse';
} else {
invocationType = params.InvocationType;
}

if (invocationType !== 'Event' && invocationType !== 'RequestResponse') {
callback(new Error(`InvocationType '${invocationType}' is incorrect, should be 'Event' or 'RequestResponse'`), null);
return;
}

const clientContext = params.ClientContext ? params.ClientContext : '';
const payload = params.Payload;
const qualifier = params.Qualifier;

if (!Util.isValidQualifier(qualifier)) {
callback(new Error(`Qualifier '${qualifier}' is incorrect`), null);
return;
}

const qualifierInternal = arnFields.qualifier;

// generate the right full function arn with qualifier
if (qualifierInternal && qualifier && qualifierInternal !== qualifier) {
callback(new Error(`Qualifier '${qualifier}' does not match the version in FunctionName`), null);
return;
}

const finalQualifier = qualifierInternal === undefined || qualifierInternal == null ? qualifier : qualifierInternal;

let functionArn;
if (typeof GreengrassCommon.buildFunctionArn === 'function') {
// GGC v1.9.0 or newer
functionArn = GreengrassCommon.buildFunctionArn(
arnFields.unqualifiedArn,
finalQualifier);
} else {
// older version of GGC
throw new Error('Function buildFunctionArn not found. buildFunctionArn is introduced in GGC v1.9.0. ' +
'Please check your GGC version.');
}

// verify client context is base64 encoded
if (Object.prototype.hasOwnProperty.call(params, 'ClientContext')) {
const cxt = params.ClientContext;
if (!Util.isValidContext(cxt)) {
callback(new Error('Client Context is invalid'), null);
return;
}
}

logger.debug(`Invoking local lambda ${functionArn} with payload ${payload} and client context ${clientContext}`);

this.ipc.postWork(functionArn, payload, clientContext, invocationType, (postWorkErr, invocationId) => {
if (postWorkErr) {
logger.error(`Failed to invoke function due to ${postWorkErr}`);
callback(postWorkErr, null);
return;
}

if (invocationType === 'RequestResponse') {
this.ipc.getWorkResult(functionArn, invocationId, (getWorkResultErr, body, functionErr, statusCode) => {
if (getWorkResultErr) {
logger.error(`Failed to get work result due to ${getWorkResultErr}`);
callback(getWorkResultErr, null);
return;
}
const data = {
FunctionError: functionErr,
StatusCode: statusCode,
Payload: body,
};
callback(null, data);
});
} else {
callback(null, invocationId);
}
});
}
}

module.exports = Lambda;
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
const Buffer = require('buffer').Buffer;

const Lambda = require('./lambda');
const Util = require('./util');
const GreengrassCommon = require('aws-greengrass-common-js');

const KEY_SECRET_ID = 'SecretId';
const KEY_VERSION_ID = 'VersionId';
const KEY_VERSION_STAGE = 'VersionStage';
const KEY_SECRET_ARN = 'ARN';
const KEY_SECRET_NAME = 'Name';
const KEY_CREATED_DATE = 'CreatedDate';

const envVars = GreengrassCommon.envVars;
const SECRETS_MANAGER_FUNCTION_ARN = envVars.SECRETS_MANAGER_FUNCTION_ARN;

class SecretsManager {
constructor() {
this.lambda = new Lambda();
}

getSecretValue(params, callback) {
const secretId = Util.getRequiredParameter(params, KEY_SECRET_ID);
const versionId = Util.getRequiredParameter(params, KEY_VERSION_ID);
const versionStage = Util.getRequiredParameter(params, KEY_VERSION_STAGE);

if (secretId === undefined) {
callback(new Error(`"${KEY_SECRET_ID}" is a required parameter`), null);
return;
}
// TODO: Remove this once we support query by VersionId
if (versionId !== undefined) {
callback(new Error('Query by VersionId is not yet supported'), null);
return;
}
if (versionId !== undefined && versionStage !== undefined) {
callback(new Error('VersionId and VersionStage cannot both be specified at the same time'), null);
return;
}

const getSecretValueRequestBytes =
SecretsManager._generateGetSecretValueRequestBytes(secretId, versionId, versionStage);

const invokeParams = {
FunctionName: SECRETS_MANAGER_FUNCTION_ARN,
Payload: getSecretValueRequestBytes,
};

console.log(`Getting secret value from secrets manager: ${getSecretValueRequestBytes}`);

this.lambda.invoke(invokeParams, (err, data) => {
if (err) {
callback(err, null); // an error occurred
} else if (SecretsManager._is200Response(data.Payload)) {
callback(null, data.Payload); // successful response
} else {
callback(new Error(JSON.stringify(data.Payload)), null); // error response
}
});
}

static _generateGetSecretValueRequestBytes(secretId, versionId, versionStage) {
const request = {
SecretId: secretId,
};

if (versionStage !== undefined) {
request.VersionStage = versionStage;
}

if (versionId !== undefined) {
request.VersionId = versionId;
}

return Buffer.from(JSON.stringify(request));
}

static _is200Response(payload) {
const hasSecretArn = this._stringContains(payload, KEY_SECRET_ARN);
const hasSecretName = this._stringContains(payload, KEY_SECRET_NAME);
const hasVersionId = this._stringContains(payload, KEY_VERSION_ID);
const hasCreatedDate = this._stringContains(payload, KEY_CREATED_DATE);

return hasSecretArn && hasSecretName && hasVersionId && hasCreatedDate;
}

static _stringContains(src, target) {
return src.indexOf(target) > -1;
}
}

module.exports = SecretsManager;
Loading