From 0493599470ccd8f41e646476c591f64628e53d55 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 12 Nov 2021 17:18:35 -0500 Subject: [PATCH] Improve github actions workflows (#409) Signed-off-by: jsetton --- .github/scripts/generateDeployConfig.js | 208 ++++++++++++++++++ .../scripts/submitSkillForCertification.js | 74 +++++++ .../scripts}/updateSkillLocale.js | 48 ++-- .github/workflows/ci.yml | 22 +- .../workflows/{deploy.yml => deployment.yml} | 44 ++-- .../workflows/{doc.yml => documentation.yml} | 0 .../{locale.yml => localization.yml} | 6 +- .github/workflows/security.yml | 26 +++ ask-resources.json | 2 +- infrastructure/cfn-deployer/skill-stack.json | 196 +++++++++++++++++ infrastructure/cfn-deployer/skill-stack.yml | 98 --------- tools/generateDeployConfig.js | 159 ------------- 12 files changed, 554 insertions(+), 329 deletions(-) create mode 100644 .github/scripts/generateDeployConfig.js create mode 100644 .github/scripts/submitSkillForCertification.js rename {tools => .github/scripts}/updateSkillLocale.js (66%) rename .github/workflows/{deploy.yml => deployment.yml} (53%) rename .github/workflows/{doc.yml => documentation.yml} (100%) rename .github/workflows/{locale.yml => localization.yml} (86%) create mode 100644 .github/workflows/security.yml create mode 100644 infrastructure/cfn-deployer/skill-stack.json delete mode 100644 infrastructure/cfn-deployer/skill-stack.yml delete mode 100644 tools/generateDeployConfig.js diff --git a/.github/scripts/generateDeployConfig.js b/.github/scripts/generateDeployConfig.js new file mode 100644 index 00000000..4417a325 --- /dev/null +++ b/.github/scripts/generateDeployConfig.js @@ -0,0 +1,208 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +/** + * Defines ask-cli resources file path + * @type {String} + */ +const ASK_CLI_RESOURCES_FILE = path.resolve('ask-resources.json'); + +/** + * Defines ask-cli states file path + * @type {String} + */ +const ASK_CLI_STATES_FILE = path.resolve('.ask', 'ask-states.json'); + +/** + * Defines skill infrastructure template file path + * @type {String} + */ +const SKILL_INFRA_TEMPLATE_FILE = path.resolve('infrastructure', 'cfn-deployer', 'skill-stack.json'); + +/** + * Defines skill manifest file path + * @type {String} + */ +const SKILL_MANIFEST_FILE = path.resolve('skill-package', 'skill.json'); + +/** + * Defines supported deployment regions + * https://developer.amazon.com/docs/smapi/skill-manifest.html#regions + * @type {Array} + */ +const SUPPORTED_DEPLOY_REGIONS = ['NA', 'EU', 'FE']; + +/** + * Defines ask-cli deployment profile name + * https://developer.amazon.com/en-US/blogs/alexa/alexa-skills-kit/2020/06/using-the-ask-cli-v2-0-to-continuously-deploy-your-skill + * @type {String} + */ +const PROFILE_NAME = '__ENVIRONMENT_ASK_PROFILE__'; + +/** + * Loads a schema + * @param {String} file + * @return {Object} + */ +function loadSchema(file) { + try { + return require(file); + } catch { + throw new Error(`Failed to load schema: ${file}`); + } +} + +/** + * Saves a schema + * @param {Object} schema + * @param {String} file + */ +function saveSchema(schema, file) { + try { + // Create the file's directory recursively in case it doesn't exist + fs.mkdirSync(path.dirname(file), { recursive: true }); + // Write json formatted schema to file + fs.writeFileSync(file, JSON.stringify(schema, null, 2)); + } catch { + throw new Error(`Failed to save schema: ${file}`); + } +} + +/** + * Creates ask-cli states + */ +function createAskCliStates() { + // Initialize ask-cli states schema + const schema = { askcliStatesVersion: '2020-03-31', profiles: {} }; + // Define state profile + const profile = { + skillId: process.env.SKILL_ID, + skillInfrastructure: { '@ask-cli/cfn-deployer': { deployState: {} } } + }; + // Define skill infrastructure deploy state + const deployState = profile.skillInfrastructure['@ask-cli/cfn-deployer'].deployState; + // Set default deploy state + deployState.default = { + s3: { bucket: process.env[`S3_BUCKET_${SUPPORTED_DEPLOY_REGIONS[0]}`], key: 'endpoint/build.zip' }, + stackId: process.env[`STACK_ID_${SUPPORTED_DEPLOY_REGIONS[0]}`] + }; + // Set regional deploy states + SUPPORTED_DEPLOY_REGIONS.forEach((region) => deployState[region] = { + s3: { bucket: process.env[`S3_BUCKET_${region}`], key: 'endpoint/build.zip' }, + stackId: process.env[`STACK_ID_${region}`] + }); + // Add state profile to schema + schema.profiles[PROFILE_NAME] = profile; + // Save ask-cli states schema + saveSchema(schema, ASK_CLI_STATES_FILE); +} + +/** + * Updates ask-cli resources + */ +function updateAskCliResources() { + // Load ask-cli resources schema + const schema = loadSchema(ASK_CLI_RESOURCES_FILE); + // Deep clone default profile as deployment profile + const profile = JSON.parse(JSON.stringify(schema.profiles.default)); + // Set regional endpoints + SUPPORTED_DEPLOY_REGIONS.forEach((region) => profile.code[region] = profile.code.default); + // Define skill infrastructure user config + const userConfig = profile.skillInfrastructure.userConfig; + // Set lambda function name + userConfig.cfn.parameters.LambdaFunctionName = process.env.FUNCTION_NAME; + // Set lambda log level + userConfig.cfn.parameters.LambdaLogLevel = process.env.LOG_LEVEL; + // Set openhab base url + userConfig.cfn.parameters.OpenHABBaseURL = process.env.OPENHAB_BASE_URL; + // Add deployment profile to schema + schema.profiles[PROFILE_NAME] = profile; + // Save ask-cli resources schema + saveSchema(schema, ASK_CLI_RESOURCES_FILE); +} + +/** + * Updates skill infrastructure template + */ +function updateSkillInfraTemplate() { + // Load skill infrastructure template schema + const schema = loadSchema(SKILL_INFRA_TEMPLATE_FILE); + // Get skill function revision + const revision = execSync('git rev-parse --short HEAD:lambda').toString().trim(); + // Define skill function version resource name + const versionResource = `AlexaSkillFunctionVersion${revision}`; + // Add skill function version resource + schema.Resources[versionResource] = { + Type: 'AWS::Lambda::Version', + DeletionPolicy: 'Retain', + Properties: { + FunctionName: { Ref: 'AlexaSkillFunction' } + } + }; + // Define skill function version permission resource name + const permissionResource = `AlexaSkillFunctionPermission${revision}`; + // Add skill function version permission resource + schema.Resources[permissionResource] = { + Type: 'AWS::Lambda::Permission', + DeletionPolicy: 'Retain', + Properties: { + ...schema.Resources.AlexaSkillFunctionPermission.Properties, + FunctionName: { Ref: versionResource } + } + }; + // Update skill endpoint output value + schema.Outputs.SkillEndpoint.Value = { Ref: versionResource }; + // Save skill infrastructure template schema + saveSchema(schema, SKILL_INFRA_TEMPLATE_FILE); +} + +/** + * Updates skill manifest + */ +function updateSkillManifest() { + // Load skill manifest schema + const schema = loadSchema(SKILL_MANIFEST_FILE); + // Extract publishing information from manifest + const { publishingInformation } = schema.manifest; + // Set publishing distribution mode as public + publishingInformation.distributionMode = 'PUBLIC'; + // Set publishing testing instructions username and passowrd + publishingInformation.testingInstructions = publishingInformation.testingInstructions + .replace('%TESTING_USERNAME%', process.env.TESTING_USERNAME) + .replace('%TESTING_PASSWORD%', process.env.TESTING_PASSWORD); + // Save skill manifest schema + saveSchema(schema, SKILL_MANIFEST_FILE); +} + +if (require.main === module) { + try { + // Create ask-cli states + createAskCliStates(); + // Update ask-cli resources + updateAskCliResources(); + // Update skill config for live deployment + if (process.env.DEPLOY_ENV === 'live') { + // Update skill infrastructure template + updateSkillInfraTemplate(); + // Update skill manifest + updateSkillManifest(); + } + } catch (error) { + console.log(error.message); + process.exit(1); + } +} diff --git a/.github/scripts/submitSkillForCertification.js b/.github/scripts/submitSkillForCertification.js new file mode 100644 index 00000000..dce38f0b --- /dev/null +++ b/.github/scripts/submitSkillForCertification.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +const assert = require('assert'); +const { execSync } = require('child_process'); +const path = require('path'); + +/** + * Defines skill manifest file path + * @type {String} + */ +const SKILL_MANIFEST_FILE = path.resolve('skill-package', 'skill.json'); + +/** + * Submits skill for certification + * https://developer.amazon.com/docs/smapi/ask-cli-command-reference.html#submit-skill-for-certification-subcommand + */ +function submitSkillForCertification() { + const method = 'AUTO_PUBLISHING'; + const message = `Release ${process.env.RELEASE_VERSION}`; + const command = + 'ask smapi submit-skill-for-certification ' + + `-s ${process.env.SKILL_ID} --publication-method ${method} --version-message "${message}"`; + + execSync(command, { stdio: 'inherit' }); +} + +/** + * Validates skill + * https://developer.amazon.com/docs/smapi/ask-cli-command-reference.html#submit-skill-validation-subcommand + * https://developer.amazon.com/docs/smapi/ask-cli-command-reference.html#get-skill-validations-subcommand + */ +function validateSkill() { + // Retrieve skill locales from manifest + const { manifest } = require(SKILL_MANIFEST_FILE); + const locales = Object.keys(manifest.publishingInformation.locales).join(','); + + // Request skill validation + const submitCommand = `ask smapi submit-skill-validation -s ${process.env.SKILL_ID} -l ${locales} -g development`; + const { id } = JSON.parse(execSync(submitCommand).toString()); + + // Get skill validations result + const getCommand = `ask smapi get-skill-validations -s ${process.env.SKILL_ID} -i ${id} -g development`; + const { message, result, status } = JSON.parse(execSync(getCommand).toString()); + + // Throw error message if provided + if (message) throw new Error(message); + // Log result if provided + if (result) console.log('Result:', JSON.stringify(result, null, 2)); + // Assert if status is successful + assert.equal(status, 'SUCCESSFUL', `Skill validation ${status.replace(/_/g, ' ').toLowerCase()}`); +} + +if (require.main === module) { + try { + // Validate skill + validateSkill(); + // Submit skill for certification + submitSkillForCertification(); + } catch (error) { + console.log(error.message); + process.exit(1); + } +} diff --git a/tools/updateSkillLocale.js b/.github/scripts/updateSkillLocale.js similarity index 66% rename from tools/updateSkillLocale.js rename to .github/scripts/updateSkillLocale.js index 3ecad47e..91c8f15b 100644 --- a/tools/updateSkillLocale.js +++ b/.github/scripts/updateSkillLocale.js @@ -15,22 +15,22 @@ const fs = require('fs'); const path = require('path'); /** - * Defines catalog path + * Defines catalog file path * @type {String} */ -const CATALOG_PATH = path.join('..', 'lambda', 'catalog.json'); +const CATALOG_FILE = path.resolve('lambda', 'catalog.json'); /** - * Defines skill manifest path + * Defines skill manifest file path * @type {String} */ -const SKILL_MANIFEST_PATH = path.join('..', 'skill-package', 'skill.json'); +const SKILL_MANIFEST_FILE = path.resolve('skill-package', 'skill.json'); /** - * Defines resources locales path + * Defines resources locales directory path * @type {String} */ -const RESOURCES_LOCALES_PATH = path.join('..', 'resources', 'locales'); +const RESOURCES_LOCALES_DIR = path.resolve('resources', 'locales'); /** * Defines supported skill Locales @@ -57,27 +57,27 @@ const SUPPORTED_SKILL_LOCALES = [ /** * Load a schema - * @param {String} path + * @param {String} file * @return {Object} */ -function loadSchema(path) { +function loadSchema(file) { try { - return require(path); + return require(file); } catch { - throw new Error(`Failed to load schema: ${path}`); + throw new Error(`Failed to load schema: ${file}`); } } /** * Save a schema * @param {Object} schema - * @param {String} path + * @param {String} file */ -function saveSchema(schema, path) { +function saveSchema(schema, file) { try { - fs.writeFileSync(path, JSON.stringify(schema, null, 2)); + fs.writeFileSync(file, JSON.stringify(schema, null, 2)); } catch { - throw new Error(`Failed to save schema: ${path}`); + throw new Error(`Failed to save schema: ${file}`); } } @@ -91,9 +91,9 @@ function updateCatalog() { const schema = {}; // Iterate over locale resources for (const locale of SUPPORTED_SKILL_LOCALES) { - const catalogPath = path.join(RESOURCES_LOCALES_PATH, locale.split('-')[0], 'catalog.json'); - if (fs.existsSync(catalogPath)) { - const { assetIds } = loadSchema(catalogPath); + const catalogFile = path.resolve(RESOURCES_LOCALES_DIR, locale.split('-')[0], 'catalog.json'); + if (fs.existsSync(catalogFile)) { + const { assetIds } = loadSchema(catalogFile); // Update catalog asset ids locale labels for (const [assetId, value] of Object.entries(assetIds)) { const labels = value.split(',').map((value) => ({ text: value.trim(), locale })); @@ -102,7 +102,7 @@ function updateCatalog() { } } // Save catalog schema - saveSchema(schema, CATALOG_PATH); + saveSchema(schema, CATALOG_FILE); } /** @@ -110,12 +110,12 @@ function updateCatalog() { */ function updateSkillManifest() { // Load skill manifest schema - const schema = loadSchema(SKILL_MANIFEST_PATH); + const schema = loadSchema(SKILL_MANIFEST_FILE); // Iterate over locale resources for (const locale of SUPPORTED_SKILL_LOCALES) { - const manifestPath = path.join(RESOURCES_LOCALES_PATH, locale.split('-')[0], 'manifest.json'); - if (fs.existsSync(manifestPath)) { - const properties = loadSchema(manifestPath); + const manifestFile = path.resolve(RESOURCES_LOCALES_DIR, locale.split('-')[0], 'manifest.json'); + if (fs.existsSync(manifestFile)) { + const properties = loadSchema(manifestFile); // Update skill manifest locale properties for (const [key, value] of Object.entries(properties)) { if (typeof schema.manifest[key] === 'object') { @@ -125,13 +125,11 @@ function updateSkillManifest() { } } // Save skill manifest schema - saveSchema(schema, SKILL_MANIFEST_PATH); + saveSchema(schema, SKILL_MANIFEST_FILE); } if (require.main === module) { try { - // Change working directory to script location - process.chdir(__dirname); // Update catalog updateCatalog(); // Update skill manifest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63dc1363..d88f74bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,14 +3,14 @@ name: CI on: push: branches: [ main ] - paths: [ lambda/** ] + paths: [ 'lambda/**' ] pull_request: branches: [ main ] - paths: [ lambda/** ] + paths: [ 'lambda/**' ] jobs: test: - name: Code Testing + name: Testing runs-on: ubuntu-latest strategy: @@ -41,19 +41,3 @@ jobs: with: name: coverage-report path: lambda/coverage - - codeql: - name: Code Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: javascript - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deployment.yml similarity index 53% rename from .github/workflows/deploy.yml rename to .github/workflows/deployment.yml index 37e52f3d..8aa1fda9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deployment.yml @@ -3,17 +3,6 @@ name: Deployment on: release: types: [ published ] - workflow_dispatch: - inputs: - pr: - description: Pull Request (development) - required: false - tag: - description: Release Version (live) - required: false - -env: - DEPLOY_ENV: ${{ (github.event_name == 'release' || github.event.inputs.tag) && 'live' || 'development' }} jobs: skill: @@ -23,8 +12,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - with: - ref: ${{ github.event.inputs.pr && format('refs/pull/{0}/merge', github.event.inputs.pr) || github.event.inputs.tag }} - name: Set up Node.js uses: actions/setup-node@v2 @@ -36,31 +23,40 @@ jobs: - name: Install ASK CLI run: npm install -g jsetton/ask-cli - - name: Cache ASK CLI states - uses: actions/cache@v2 - with: - path: .ask/ask-states.json - key: ask-states-${{ env.DEPLOY_ENV }} - - name: Generate deployment config - run: node tools/generateDeployConfig.js + run: node .github/scripts/generateDeployConfig.js env: - FUNCTION_NAME: ${{ env.DEPLOY_ENV == 'live' && 'openhab-alexa' || 'openhab-alexa-beta' }} - LOG_LEVEL: ${{ env.DEPLOY_ENV == 'live' && 'error' || 'info' }} + DEPLOY_ENV: ${{ github.event.release.prerelease && 'development' || 'live' }} + FUNCTION_NAME: openhab-alexa + LOG_LEVEL: ${{ github.event.release.prerelease && 'info' || 'error' }} + OPENHAB_BASE_URL: https://myopenhab.org S3_BUCKET_NA: ${{ secrets.S3_BUCKET_NA }} S3_BUCKET_EU: ${{ secrets.S3_BUCKET_EU }} S3_BUCKET_FE: ${{ secrets.S3_BUCKET_FE }} SKILL_ID: ${{ secrets.SKILL_ID }} + STACK_ID_NA: ${{ secrets.STACK_ID_NA }} + STACK_ID_EU: ${{ secrets.STACK_ID_EU }} + STACK_ID_FE: ${{ secrets.STACK_ID_FE }} TESTING_USERNAME: ${{ secrets.TESTING_USERNAME }} TESTING_PASSWORD: ${{ secrets.TESTING_PASSWORD }} - name: Deploy skill and AWS resources run: ask deploy env: - AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }} + ASK_ACCESS_TOKEN: ${{ secrets.ASK_ACCESS_TOKEN }} + ASK_REFRESH_TOKEN : ${{ secrets.ASK_REFRESH_TOKEN }} + ASK_VENDOR_ID: ${{ secrets.ASK_VENDOR_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Submit skill for certification + if: github.event.release.prerelease != true + run: node .github/scripts/submitSkillForCertification.js + env: ASK_ACCESS_TOKEN: ${{ secrets.ASK_ACCESS_TOKEN }} ASK_REFRESH_TOKEN : ${{ secrets.ASK_REFRESH_TOKEN }} ASK_VENDOR_ID: ${{ secrets.ASK_VENDOR_ID }} - ASK_SHARE_USAGE: false + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + RELEASE_VERSION: ${{ github.event.release.tag_name }} + SKILL_ID: ${{ secrets.SKILL_ID }} diff --git a/.github/workflows/doc.yml b/.github/workflows/documentation.yml similarity index 100% rename from .github/workflows/doc.yml rename to .github/workflows/documentation.yml diff --git a/.github/workflows/locale.yml b/.github/workflows/localization.yml similarity index 86% rename from .github/workflows/locale.yml rename to .github/workflows/localization.yml index cca1b2d8..c1150034 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/localization.yml @@ -1,9 +1,9 @@ -name: Locale +name: Localization on: push: branches: [ main ] - paths: [ resources/locales/** ] + paths: [ 'resources/locales/**' ] jobs: update: @@ -20,7 +20,7 @@ jobs: node-version: 14.x - name: Update skill locale - run: node tools/updateSkillLocale.js + run: node .github/scripts/updateSkillLocale.js - name: Check for changes to commit id: changes diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..2a8b42a8 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,26 @@ +name: Security + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '42 1 1 * *' # Every first day of the month at 1:42 + +jobs: + codeql: + name: Code Scanning + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: javascript + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/ask-resources.json b/ask-resources.json index 2db7ed7b..cc937609 100644 --- a/ask-resources.json +++ b/ask-resources.json @@ -16,7 +16,7 @@ "awsRegion": "us-east-1", "runtime": "nodejs14.x", "handler": "index.handler", - "templatePath": "./infrastructure/cfn-deployer/skill-stack.yml", + "templatePath": "./infrastructure/cfn-deployer/skill-stack.json", "cfn": { "parameters": { "LambdaFunctionName": "alexa-openhab", diff --git a/infrastructure/cfn-deployer/skill-stack.json b/infrastructure/cfn-deployer/skill-stack.json new file mode 100644 index 00000000..f7627c91 --- /dev/null +++ b/infrastructure/cfn-deployer/skill-stack.json @@ -0,0 +1,196 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "OpenHABBaseURL": { + "Type": "String", + "Default": "https://myopenhab.org" + }, + "OpenHABUsername": { + "Type": "String", + "Default": "" + }, + "OpenHABPassword": { + "Type": "String", + "Default": "" + }, + "OpenHABCertFile": { + "Type": "String", + "Default": "ssl/client.pfx" + }, + "OpenHABCertPassphrase": { + "Type": "String", + "Default": "" + }, + "SkillId": { + "Type": "String" + }, + "LambdaFunctionName": { + "Type": "String", + "Default": "alexa-openhab" + }, + "LambdaLogLevel": { + "Type": "String", + "Default": "info", + "AllowedValues": [ + "error", + "warn", + "info", + "debug" + ] + }, + "LambdaRuntime": { + "Type": "String" + }, + "LambdaRuntime": { + "Type": "String" + }, + "LambdaHandler": { + "Type": "String" + }, + "CodeBucket": { + "Type": "String" + }, + "CodeKey": { + "Type": "String" + }, + "CodeVersion": { + "Type": "String" + } + }, + "Resources": { + "AlexaSkillIAMRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "Policies": [ + { + "PolicyName": "AlexaOpenHABSkillPolicy", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:*" + } + } + ] + } + } + ] + } + }, + "AlexaSkillFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "CodeBucket" + }, + "S3Key": { + "Ref": "CodeKey" + }, + "S3ObjectVersion": { + "Ref": "CodeVersion" + } + }, + "FunctionName": { + "Ref": "LambdaFunctionName" + }, + "Description": "openHAB Skill for Amazon Alexa", + "Handler": { + "Ref": "LambdaHandler" + }, + "Runtime": { + "Ref": "LambdaRuntime" + }, + "Role": { + "Fn::GetAtt": [ + "AlexaSkillIAMRole", + "Arn" + ] + }, + "MemorySize": 128, + "Timeout": 10, + "Environment": { + "Variables": { + "LOG_LEVEL": { + "Ref": "LambdaLogLevel" + }, + "OPENHAB_BASE_URL": { + "Ref": "OpenHABBaseURL" + }, + "OPENHAB_USERNAME": { + "Ref": "OpenHABUsername" + }, + "OPENHAB_PASSWORD": { + "Ref": "OpenHABPassword" + }, + "OPENHAB_CERT_FILE": { + "Ref": "OpenHABCertFile" + }, + "OPENHAB_CERT_PASSPHRASE": { + "Ref": "OpenHABCertPassphrase" + } + } + } + } + }, + "AlexaSkillFunctionPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AlexaSkillFunction", + "Arn" + ] + }, + "Principal": "alexa-connectedhome.amazon.com", + "EventSourceToken": { + "Ref": "SkillId" + } + } + }, + "AlexaSkillFunctionLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${AlexaSkillFunction}" + }, + "RetentionInDays": 30 + } + } + }, + "Outputs": { + "SkillEndpoint": { + "Description": "LambdaARN for the regional endpoint", + "Value": { + "Fn::GetAtt": [ + "AlexaSkillFunction", + "Arn" + ] + } + } + } +} diff --git a/infrastructure/cfn-deployer/skill-stack.yml b/infrastructure/cfn-deployer/skill-stack.yml deleted file mode 100644 index 63b04606..00000000 --- a/infrastructure/cfn-deployer/skill-stack.yml +++ /dev/null @@ -1,98 +0,0 @@ -AWSTemplateFormatVersion: 2010-09-09 -Parameters: - OpenHABBaseURL: - Type: String - Default: https://myopenhab.org - OpenHABUsername: - Type: String - Default: '' - OpenHABPassword: - Type: String - Default: '' - OpenHABCertFile: - Type: String - Default: ssl/client.pfx - OpenHABCertPassphrase: - Type: String - Default: '' - SkillId: - Type: String - LambdaFunctionName: - Type: String - Default: alexa-openhab - LambdaLogLevel: - Type: String - Default: info - AllowedValues: [error, warn, info, debug] - LambdaRuntime: - Type: String - LambdaHandler: - Type: String - CodeBucket: - Type: String - CodeKey: - Type: String - CodeVersion: - Type: String -Resources: - AlexaSkillIAMRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Action: - - sts:AssumeRole - Path: / - Policies: - - PolicyName: AlexaOpenHABSkillPolicy - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: - - logs:CreateLogStream - - logs:PutLogEvents - Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:* - AlexaSkillFunction: - Type: AWS::Lambda::Function - Properties: - Code: - S3Bucket: !Ref CodeBucket - S3Key: !Ref CodeKey - S3ObjectVersion: !Ref CodeVersion - FunctionName: !Ref LambdaFunctionName - Description: openHAB Skill for Amazon Alexa - Handler: !Ref LambdaHandler - Runtime: !Ref LambdaRuntime - Role: !GetAtt AlexaSkillIAMRole.Arn - MemorySize: 128 - Timeout: 10 - Environment: - Variables: - LOG_LEVEL: !Ref LambdaLogLevel - OPENHAB_BASE_URL: !Ref OpenHABBaseURL - OPENHAB_USERNAME: !Ref OpenHABUsername - OPENHAB_PASSWORD: !Ref OpenHABPassword - OPENHAB_CERT_FILE: !Ref OpenHABCertFile - OPENHAB_CERT_PASSPHRASE: !Ref OpenHABCertPassphrase - AlexaSkillFunctionEventPermission: - Type: AWS::Lambda::Permission - Properties: - Action: lambda:invokeFunction - FunctionName: !GetAtt AlexaSkillFunction.Arn - Principal: alexa-connectedhome.amazon.com - EventSourceToken: !Ref SkillId - AlexaSkillFunctionLogGroup: - Type: AWS::Logs::LogGroup - Properties: - LogGroupName: !Sub /aws/lambda/${AlexaSkillFunction} - RetentionInDays: 30 -Outputs: - SkillEndpoint: - Description: LambdaARN for the regional endpoint - Value: !GetAtt AlexaSkillFunction.Arn diff --git a/tools/generateDeployConfig.js b/tools/generateDeployConfig.js deleted file mode 100644 index 6bdfc63e..00000000 --- a/tools/generateDeployConfig.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ - -const fs = require('fs'); -const path = require('path'); - -/** - * Defines ask-cli resources file - * @type {String} - */ -const ASK_CLI_RESOURCES_FILE = path.join('..', 'ask-resources.json'); - -/** - * Defines ask-cli states file - * @type {String} - */ -const ASK_CLI_STATES_FILE = path.join('..', '.ask', 'ask-states.json'); - -/** - * Defines skill manifest file - * @type {String} - */ -const SKILL_MANIFEST_FILE = path.join('..', 'skill-package', 'skill.json'); - -/** - * Defines supported deployment regions - * https://developer.amazon.com/docs/smapi/skill-manifest.html#regions - * @type {Array} - */ -const SUPPORTED_DEPLOY_REGIONS = ['NA', 'EU', 'FE']; - -/** - * Defines ask-cli deployment profile name - * https://developer.amazon.com/en-US/blogs/alexa/alexa-skills-kit/2020/06/using-the-ask-cli-v2-0-to-continuously-deploy-your-skill - * @type {String} - */ -const PROFILE_NAME = '__ENVIRONMENT_ASK_PROFILE__'; - -/** - * Loads a schema - * @param {String} file - * @return {Object} - */ -function loadSchema(file) { - try { - return require(file); - } catch { - throw new Error(`Failed to load schema: ${file}`); - } -} - -/** - * Saves a schema - * @param {Object} schema - * @param {String} file - */ -function saveSchema(schema, file) { - try { - // Create the file's directory recursively in case it doesn't exist - fs.mkdirSync(path.dirname(file), { recursive: true }); - // Write json formatted schema to file - fs.writeFileSync(file, JSON.stringify(schema, null, 2)); - } catch { - throw new Error(`Failed to save schema: ${file}`); - } -} - -/** - * Updates ask-cli resources - */ -function updateAskCliResources() { - // Load ask-cli resources schema - const schema = loadSchema(ASK_CLI_RESOURCES_FILE); - // Deep clone default profile as deployment profile - const profile = JSON.parse(JSON.stringify(schema.profiles.default)); - // Define skill infrastructure user config - const config = profile.skillInfrastructure.userConfig; - // Set lambda function name - config.cfn.parameters.LambdaFunctionName = process.env.FUNCTION_NAME || 'openhab-alexa'; - // Set lambda log level to error - config.cfn.parameters.LambdaLogLevel = process.env.LOG_LEVEL || 'error'; - // Set default s3 artifact bucket name - config.artifactsS3 = { bucketName: process.env[`S3_BUCKET_${SUPPORTED_DEPLOY_REGIONS[0]}`] }; - // Initialize regional overrides object - config.regionalOverrides = {}; - // Set regional resources - for (const region of SUPPORTED_DEPLOY_REGIONS) { - // Set regional endpoint - profile.code[region] = profile.code.default; - // Set regional s3 artifact bucket name - config.regionalOverrides[region] = { artifactsS3: { bucketName: process.env[`S3_BUCKET_${region}`] } }; - } - // Add deployment profile to schema - schema.profiles[PROFILE_NAME] = profile; - // Save ask-cli resources schema - saveSchema(schema, ASK_CLI_RESOURCES_FILE); -} - -/** - * Updates ask-cli states - */ -function updateAskCliStates() { - // Load ask-cli states schema if available - const schema = fs.existsSync(ASK_CLI_STATES_FILE) - ? loadSchema(ASK_CLI_STATES_FILE) - : { askcliStatesVersion: '2020-03-31', profiles: {} }; - // Define deployment profile - const profile = schema.profiles[PROFILE_NAME] || {}; - // Set skill id - profile.skillId = process.env.SKILL_ID; - // Update deployment profile - schema.profiles[PROFILE_NAME] = profile; - // Save ask-cli states schema - saveSchema(schema, ASK_CLI_STATES_FILE); -} - -/** - * Updates skill manifest - */ -function updateSkillManifest() { - // Load skill manifest schema - const schema = loadSchema(SKILL_MANIFEST_FILE); - // Extract publishing information from manifest - const { publishingInformation } = schema.manifest; - // Set publishing distribution mode as public - publishingInformation.distributionMode = 'PUBLIC'; - // Set publishing testing instructions username and passowrd - publishingInformation.testingInstructions = publishingInformation.testingInstructions - .replace('%TESTING_USERNAME%', process.env.TESTING_USERNAME || 'N/A') - .replace('%TESTING_PASSWORD%', process.env.TESTING_PASSWORD || 'N/A'); - // Save skill manifest schema - saveSchema(schema, SKILL_MANIFEST_FILE); -} - -if (require.main === module) { - try { - // Change working directory to script location - process.chdir(__dirname); - // Update ask-cli resources - updateAskCliResources(); - // Update ask-cli states - updateAskCliStates(); - // Update skill manifest - updateSkillManifest(); - } catch (error) { - console.log(error.message); - process.exit(1); - } -}