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

feat: add skill events property to ask-resources.json #410

Merged
merged 1 commit into from
Aug 4, 2022
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
72 changes: 43 additions & 29 deletions docs/concepts/Alexa-Skill-Project-Definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,51 +26,66 @@ The `SkillInfrastructure` represents the configuration on how to deploy skill's
Please check the `skillInfrastructure` field in the example below for its representation in project config.

# Project Config For Resources Management
Below shows the example how CLI tracks user's config and the deployment states, using `@ask-cli/cfn-deployer` deployer as an example.
Below shows the example how CLI tracks user's config and the deployment states, using `@ask-cli/cfn-deployer` deployer as an example.

### ask-resources.json (this file is up to configure!)
```jsonc
{
// each config is tracked specific to each profile
"profiles": {
"{profileName}": {
"profiles": {
"{profileName}": { // profile name-specific config

// skillMetadata tracks Alexa skill's build-time JSONs
"skillMetadata": {
"src": "./skill-package", // the source folder for skill package (either relative path or absolute path)
"lastDeployHash": "{hashResult}" // CLI internal data to optimize the deploy flow
"skillMetadata": { // Alexa skill metadata to deploy
"src": "./skill-package", // source folder for skill package (either relative path or absolute path)
"lastDeployHash": "{hashResult}" // CLI internal data to optimize the deploy flow
},

// code owns Alexa skill code to be built and hosted
"code": {
"code": { // Alexa skill code to be built and hosted
"default": { // region for the codebase
"src": "./code" // the source folder for codebase
"src": "./code" // source folder for codebase
},
"{supportedRegion}": { // the supported regions are always in sync with Alexa, which includes default, NA, EU, FE.
"{supportedRegion}": { // supported regions are always in sync with Alexa, which includes default, NA, EU, FE.
"src": "./code"
}
},

// skillInfrastructure tracks the settings and states for skill api-endpoint's deployer
"skillInfrastructure": {
"type": "@ask-cli/cfn-deployer", // selected deployer to invoke when deploy
"userConfig": { // tracks settings for the deployer, deployer-specific
"skillInfrastructure": { // Alexa skill infrastructure to deploy
"type": "@ask-cli/cfn-deployer", // deployer type
"userConfig": { // deployer-specific config
"awsRegion": "{aws-region}",
"runtime": "{lambdaRuntime}",
"handler": "{lambdaHandler}",
"templatePath": "stack.yaml",
"targetEndpoint": ["alexaForBusiness", "custom", "flashBriefing", "health", "householdList", "music", "smartHome", "video", "events"], // set the targetEndpoint with the value(s) from https://developer.amazon.com/en-US/docs/alexa/smapi/skill-manifest.html#api-enumeration or events to target events endpoint. Defaults to api endpoints from skill manifest if not specified.
"artifactsS3": { // custom s3 configuration to upload skill build artifact (zip, jar,etc)
"bucketName": "{bucketName}", // custom bucket name to store artifacts
"bucketKey": "{bucketKey.zip}" // custom bucket object key
"targetEndpoint": [ // defaults to api names from skill manifest if not specified
"alexaForBusiness",
"custom",
"flashBriefing",
"health",
"householdList",
"music",
"smartHome",
"video"
],
"skillEvents": { // skill events support for deployer endpoint(s)
jsetton marked this conversation as resolved.
Show resolved Hide resolved
"publications": [ // list of proactive event names
"{proactiveEventName1}",
...
],
"subscriptions": [ // list of skill/list event names
"{skillOrListEventName1}",
...
]
},
"artifactsS3": { // custom s3 configuration to upload skill build artifact (zip, jar, etc)
"bucketName": "{bucketName}", // custom bucket name to store artifacts
"bucketKey": "{bucketKey.zip}" // custom bucket object key
},
"cfn": {
"parameters": { // additional parameters to pass to the CloudFormation
"parameters": { // additional parameters to pass to the CloudFormation
"SomeUserParameter1Key": "some value",
"SomeUserParameter2Key": "another value"
},
"capabilities": [ // additional capabilities to pass to the CloudFormation. CAPABILITY_IAM capability is always passed by default.
"CAPABILITY_NAMED_IAM"
"capabilities": [ // additional capabilities to pass to the CloudFormation
"CAPABILITY_NAMED_IAM" // CAPABILITY_IAM is always passed by default
]
}
}
Expand All @@ -95,21 +110,21 @@ Below shows the example how CLI tracks user's config and the deployment states,
"default": {
"lastDeployHash": "{hashResult}" // CLI internal data to optimize the deploy flow
},
"{supportedRegion}": { // the supported regions are always in sync with Alexa, which includes default, NA, EU, FE.
"{supportedRegion}": { // supported regions are always in sync with Alexa, which includes default, NA, EU, FE.
"lastDeployHash": "{hashResult}"
}
},

"skillInfrastructure": {
"@ask-cli/cfn-deployer": { // selected deployer to invoke when deploy
"deployState": { // tracks states for the deployer to continuously deploy, deployer-specific
"@ask-cli/cfn-deployer": { // deployer type
"deployState": { // deployer-specific states
"default": {
"s3": {
"bucket": "{bucket}",
"key": "{key}",
"objectVersion": "{version}"
},
"outputs": [ // outputs from the CloudFormation deploy
"outputs": [ // outputs from the CloudFormation deploy
{
"OutputKey": "{outputKey}",
"OutputValue": "{outputValue}",
Expand All @@ -121,8 +136,7 @@ Below shows the example how CLI tracks user's config and the deployment states,
"{supportedRegion}": { ... }
}
}
},

}
}
}
}
Expand Down
24 changes: 18 additions & 6 deletions lib/controllers/skill-infrastructure-controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,28 @@ module.exports = class SkillInfrastructureController {
const targetEndpoints = ResourcesConfig.getInstance().getTargetEndpoints(this.profile);
// for backward compatibility, defaulting to api from skill manifest if targetEndpoints is not defined
const domains = targetEndpoints.length ? targetEndpoints : Object.keys(Manifest.getInstance().getApis());
// 1.update local skill.json file: update the "uri" in all target endpoints for each region
// 1.update local skill.json file:
// update the "uri" in all target api endpoints for each region
domains.forEach((domain) => {
R.keys(rawDeployResult).forEach((region) => {
if (domain === Manifest.endpointTypes.EVENTS) {
Manifest.getInstance().setEventsEndpointByRegion(region, rawDeployResult[region].endpoint);
} else {
Manifest.getInstance().setApisEndpointByDomainRegion(domain, region, rawDeployResult[region].endpoint);
}
Manifest.getInstance().setApisEndpointByDomainRegion(domain, region, rawDeployResult[region].endpoint);
});
});
// add skill events if defined in resources config
const events = ResourcesConfig.getInstance().getSkillEvents(this.profile);
if (events) {
R.keys(rawDeployResult).forEach((region) => {
Manifest.getInstance().setEventsEndpointByRegion(region, rawDeployResult[region].endpoint);
});
if (events.publications) {
const publications = events.publications.map((eventName) => ({ eventName }));
Manifest.getInstance().setEventsPublications(publications);
}
if (events.subscriptions) {
const subscriptions = events.subscriptions.map((eventName) => ({ eventName }));
Manifest.getInstance().setEventsSubscriptions(subscriptions);
}
}

Manifest.getInstance().write();
// 2.compare with current hash result to decide if skill.json file need to be updated
Expand Down
16 changes: 16 additions & 0 deletions lib/model/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,20 @@ module.exports = class Manifest extends ConfigFile {
this.setProperty(['manifest', Manifest.endpointTypes.APIS, domain, 'regions', region, 'endpoint'], endpointObj);
}
}

getEventsPublications() {
return this.getProperty(['manifest', Manifest.endpointTypes.EVENTS, 'publications']);
}

setEventsPublications(publications) {
this.setProperty(['manifest', Manifest.endpointTypes.EVENTS, 'publications'], publications);
}

getEventsSubscriptions() {
return this.getProperty(['manifest', Manifest.endpointTypes.EVENTS, 'subscriptions']);
}

setEventsSubscriptions(subscriptions) {
this.setProperty(['manifest', Manifest.endpointTypes.EVENTS, 'subscriptions'], subscriptions);
}
};
4 changes: 4 additions & 0 deletions lib/model/resources-config/ask-resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ module.exports = class AskResources extends ConfigFile {
getTargetEndpoint(profile) {
return this.getProperty(['profiles', profile, 'skillInfrastructure', 'userConfig', 'targetEndpoint']) || [];
}

getSkillEvents(profile) {
return this.getProperty(['profiles', profile, 'skillInfrastructure', 'userConfig', 'skillEvents']);
}
};

module.exports.BASE = BASE;
4 changes: 4 additions & 0 deletions lib/model/resources-config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ module.exports = class ResourcesConfig {
return AskResources.getInstance().getTargetEndpoint(profile);
}

getSkillEvents(profile) {
return AskResources.getInstance().getSkillEvents(profile);
}

// Group for the "skillInfrastructure"
getSkillInfraType(profile) {
return AskResources.getInstance().getSkillInfraType(profile);
Expand Down
64 changes: 54 additions & 10 deletions test/unit/controller/skill-infrastructure-controller-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,41 +387,82 @@ describe('Controller test - skill infrastructure controller test', () => {
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(hashUtils, 'getHash').callsArgWith(1, null, 'TEST_HASH');
sinon.stub(SkillInfrastructureController.prototype, '_ensureSkillManifestGotUpdated').callsArgWith(0);
const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
const setApisEndpointByDomainRegionSpy = sinon.spy(Manifest.prototype, 'setApisEndpointByDomainRegion');

const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
const setEventsPublicationsSpy = sinon.spy(Manifest.prototype, 'setEventsPublications');
const setEventsSubscriptionsSpy = sinon.spy(Manifest.prototype, 'setEventsSubscriptions');
// call
skillInfraController.updateSkillManifestWithDeployResult(TEST_DEPLOY_RESULT, (err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal(undefined);
expect(setEventsEndpointByRegionSpy.callCount).eq(2);
expect(setApisEndpointByDomainRegionSpy.callCount).eq(4);
expect(setEventsEndpointByRegionSpy.callCount).eq(0);
expect(setEventsPublicationsSpy.callCount).eq(0);
expect(setEventsSubscriptionsSpy.callCount).eq(0);
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'default').url).equal('TEST_URL1');
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'EU').url).equal('TEST_URL2');
expect(ResourcesConfig.getInstance().getSkillMetaLastDeployHash(TEST_PROFILE)).equal('TEST_HASH');
done();
});
});

it('| manifest update correctly without events endpoint, expect success message and new hash set', (done) => {
it('| manifest update correctly with events endpoint only, expect success message and new hash set', (done) => {
// setup
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(hashUtils, 'getHash').callsArgWith(1, null, 'TEST_HASH');
sinon.stub(SkillInfrastructureController.prototype, '_ensureSkillManifestGotUpdated').callsArgWith(0);
sinon.stub(ResourcesConfig.prototype, 'getTargetEndpoints').returns(['custom', 'smartHome']);
const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
sinon.stub(ResourcesConfig.prototype, 'getSkillEvents').returns({});
const setApisEndpointByDomainRegionSpy = sinon.spy(Manifest.prototype, 'setApisEndpointByDomainRegion');
const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
jsetton marked this conversation as resolved.
Show resolved Hide resolved
const setEventsPublicationsSpy = sinon.spy(Manifest.prototype, 'setEventsPublications');
const setEventsSubscriptionsSpy = sinon.spy(Manifest.prototype, 'setEventsSubscriptions');
// call
skillInfraController.updateSkillManifestWithDeployResult(TEST_DEPLOY_RESULT, (err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal(undefined);
expect(setApisEndpointByDomainRegionSpy.callCount).eq(4);
expect(setEventsEndpointByRegionSpy.callCount).eq(2);
expect(setEventsPublicationsSpy.callCount).eq(0);
expect(setEventsSubscriptionsSpy.callCount).eq(0);
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'default').url).equal('TEST_URL1');
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'EU').url).equal('TEST_URL2');
expect(Manifest.getInstance().getEventsEndpointByRegion('default').url).equal('TEST_URL1');
expect(Manifest.getInstance().getEventsEndpointByRegion('EU').url).equal('TEST_URL2');
expect(ResourcesConfig.getInstance().getSkillMetaLastDeployHash(TEST_PROFILE)).equal('TEST_HASH');
done();
});
});

it('| manifest update correctly with events properties, expect success message and new hash set', (done) => {
// setup
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(hashUtils, 'getHash').callsArgWith(1, null, 'TEST_HASH');
sinon.stub(SkillInfrastructureController.prototype, '_ensureSkillManifestGotUpdated').callsArgWith(0);
sinon.stub(ResourcesConfig.prototype, 'getSkillEvents').returns({
publications: ["TEST_PUBLICATION"],
subscriptions: ["TEST_SUBSCRIPTION"]
});
const setApisEndpointByDomainRegionSpy = sinon.spy(Manifest.prototype, 'setApisEndpointByDomainRegion');
const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
const setEventsPublicationsSpy = sinon.spy(Manifest.prototype, 'setEventsPublications');
const setEventsSubscriptionsSpy = sinon.spy(Manifest.prototype, 'setEventsSubscriptions');
// call
skillInfraController.updateSkillManifestWithDeployResult(TEST_DEPLOY_RESULT, (err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal(undefined);
expect(setEventsEndpointByRegionSpy.callCount).eq(0);
expect(setApisEndpointByDomainRegionSpy.callCount).eq(4);
expect(setEventsEndpointByRegionSpy.callCount).eq(2);
expect(setEventsPublicationsSpy.callCount).eq(1);
expect(setEventsSubscriptionsSpy.callCount).eq(1);
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'default').url).equal('TEST_URL1');
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'EU').url).equal('TEST_URL2');
expect(Manifest.getInstance().getEventsEndpointByRegion('default').url).equal('TEST_URL1');
expect(Manifest.getInstance().getEventsEndpointByRegion('EU').url).equal('TEST_URL2');
expect(Manifest.getInstance().getEventsPublications()[0].eventName).equal('TEST_PUBLICATION');
expect(Manifest.getInstance().getEventsSubscriptions()[0].eventName).equal('TEST_SUBSCRIPTION');
expect(ResourcesConfig.getInstance().getSkillMetaLastDeployHash(TEST_PROFILE)).equal('TEST_HASH');
done();
});
Expand All @@ -433,16 +474,19 @@ describe('Controller test - skill infrastructure controller test', () => {
sinon.stub(hashUtils, 'getHash').callsArgWith(1, null, 'TEST_HASH');
sinon.stub(SkillInfrastructureController.prototype, '_ensureSkillManifestGotUpdated').callsArgWith(0);
sinon.stub(ResourcesConfig.prototype, 'getTargetEndpoints').returns([]);
const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
const setApisEndpointByDomainRegionSpy = sinon.spy(Manifest.prototype, 'setApisEndpointByDomainRegion');

const setEventsEndpointByRegionSpy = sinon.spy(Manifest.prototype, 'setEventsEndpointByRegion');
const setEventsPublicationsSpy = sinon.spy(Manifest.prototype, 'setEventsPublications');
const setEventsSubscriptionsSpy = sinon.spy(Manifest.prototype, 'setEventsSubscriptions');
// call
skillInfraController.updateSkillManifestWithDeployResult(TEST_DEPLOY_RESULT, (err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal(undefined);
expect(setEventsEndpointByRegionSpy.callCount).eq(0);
expect(setApisEndpointByDomainRegionSpy.callCount).eq(2);
expect(setEventsEndpointByRegionSpy.callCount).eq(0);
expect(setEventsPublicationsSpy.callCount).eq(0);
expect(setEventsSubscriptionsSpy.callCount).eq(0);
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'default').url).equal('TEST_URL1');
expect(Manifest.getInstance().getApisEndpointByDomainRegion('custom', 'EU').url).equal('TEST_URL2');
expect(ResourcesConfig.getInstance().getSkillMetaLastDeployHash(TEST_PROFILE)).equal('TEST_HASH');
Expand Down
1 change: 0 additions & 1 deletion test/unit/fixture/model/regular-proj/ask-resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"template": "./awsStacks/skill-infra.yaml",
"targetEndpoint": [
"custom",
"events",
"smartHome"
],
"regionOverrides": {
Expand Down