diff --git a/README.md b/README.md index 219896ce..0c3e77a7 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ $ aio app --help * [`aio app:add:service`](#aio-appaddservice) * [`aio app:add:web-assets`](#aio-appaddweb-assets) * [`aio app:build`](#aio-appbuild) +* [`aio app:config`](#aio-appconfig) +* [`aio app:config:get`](#aio-appconfigget) +* [`aio app:config:get:log-forwarding`](#aio-appconfiggetlog-forwarding) +* [`aio app:config:get:log-forwarding:errors`](#aio-appconfiggetlog-forwardingerrors) +* [`aio app:config:set`](#aio-appconfigset) +* [`aio app:config:set:log-forwarding`](#aio-appconfigsetlog-forwarding) * [`aio app:create [PATH]`](#aio-appcreate-path) * [`aio app:delete`](#aio-appdelete) * [`aio app:delete:action [ACTION-NAME]`](#aio-appdeleteaction-action-name) @@ -58,8 +64,6 @@ $ aio app --help ## `aio app` -Create, run, test, and deploy Adobe I/O Apps - ``` Create, run, test, and deploy Adobe I/O Apps @@ -71,12 +75,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/index.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/index.js)_ +_See code: [src/commands/app/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/index.ts)_ ## `aio app:add` -Add a new component to an existing Adobe I/O App - ``` Add a new component to an existing Adobe I/O App @@ -88,12 +90,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/add/index.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/index.js)_ +_See code: [src/commands/app/add/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/index.ts)_ ## `aio app:add:action` -Add new actions - ``` Add new actions @@ -113,12 +113,10 @@ ALIASES $ aio app:add:actions ``` -_See code: [src/commands/app/add/action.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/action.js)_ +_See code: [src/commands/app/add/action.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/action.ts)_ ## `aio app:add:ci` -Add CI files - ``` Add CI files @@ -131,12 +129,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/add/ci.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/ci.js)_ +_See code: [src/commands/app/add/ci.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/ci.ts)_ ## `aio app:add:event` -Add a new Adobe I/O Events action - ``` Add a new Adobe I/O Events action @@ -156,12 +152,10 @@ ALIASES $ aio app:add:events ``` -_See code: [src/commands/app/add/event.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/event.js)_ +_See code: [src/commands/app/add/event.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/event.ts)_ ## `aio app:add:extension` -Add new extensions or a standalone application to the project - ``` Add new extensions or a standalone application to the project @@ -182,12 +176,10 @@ ALIASES $ aio app:add:extensions ``` -_See code: [src/commands/app/add/extension.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/extension.js)_ +_See code: [src/commands/app/add/extension.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/extension.ts)_ ## `aio app:add:service` -Subscribe to Services in the current Workspace - ``` Subscribe to Services in the current Workspace @@ -203,12 +195,10 @@ ALIASES $ aio app:add:services ``` -_See code: [src/commands/app/add/service.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/service.js)_ +_See code: [src/commands/app/add/service.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/service.ts)_ ## `aio app:add:web-assets` -Add web assets support - ``` Add web assets support @@ -225,12 +215,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/add/web-assets.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/web-assets.js)_ +_See code: [src/commands/app/add/web-assets.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/add/web-assets.ts)_ ## `aio app:build` -Build an Adobe I/O App - ``` Build an Adobe I/O App @@ -270,11 +258,121 @@ DESCRIPTION This will always force a rebuild unless --no-force-build is set. ``` -_See code: [src/commands/app/build.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/build.js)_ +_See code: [src/commands/app/build.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/build.ts)_ -## `aio app:create [PATH]` +## `aio app:config` -Create a new Adobe I/O App with default parameters +``` +Manage app config + +USAGE + $ aio app:config + +OPTIONS + -v, --verbose Verbose output + --version Show version + +ALIASES + $ aio app:config + $ aio app:config +``` + +_See code: [src/commands/app/config/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/config/index.ts)_ + +## `aio app:config:get` + +``` +Get app config + +USAGE + $ aio app:config:get + +OPTIONS + -v, --verbose Verbose output + --version Show version + +ALIASES + $ aio app:config:get +``` + +_See code: [src/commands/app/config/get/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/config/get/index.ts)_ + +## `aio app:config:get:log-forwarding` + +``` +Get log forwarding destination configuration + +USAGE + $ aio app:config:get:log-forwarding + +OPTIONS + -v, --verbose Verbose output + --version Show version + +ALIASES + $ aio app:config:get:log-forwarding + $ aio app:config:get:lf +``` + +_See code: [src/commands/app/config/get/log-forwarding.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/config/get/log-forwarding.ts)_ + +## `aio app:config:get:log-forwarding:errors` + +``` +Get log forwarding errors + +USAGE + $ aio app:config:get:log-forwarding:errors + +OPTIONS + -v, --verbose Verbose output + --version Show version + +ALIASES + $ aio app:config:get:log-forwarding:errors + $ aio app:config:get:lf:errors +``` + +_See code: [src/commands/app/config/get/log-forwarding/errors.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/config/get/log-forwarding/errors.ts)_ + +## `aio app:config:set` + +``` +Set app config + +USAGE + $ aio app:config:set + +OPTIONS + -v, --verbose Verbose output + --version Show version + +ALIASES + $ aio app:config:set +``` + +_See code: [src/commands/app/config/set/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/config/set/index.ts)_ + +## `aio app:config:set:log-forwarding` + +``` +Set log forwarding destination configuration + +USAGE + $ aio app:config:set:log-forwarding + +OPTIONS + -v, --verbose Verbose output + --version Show version + +ALIASES + $ aio app:config:set:log-forwarding + $ aio app:config:set:lf +``` + +_See code: [src/commands/app/config/set/log-forwarding.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/config/set/log-forwarding.ts)_ + +## `aio app:create [PATH]` ``` Create a new Adobe I/O App with default parameters @@ -292,12 +390,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/create.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/create.js)_ +_See code: [src/commands/app/create.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/create.ts)_ ## `aio app:delete` -Delete a component from an existing Adobe I/O App - ``` Delete a component from an existing Adobe I/O App @@ -309,12 +405,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/delete/index.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/index.js)_ +_See code: [src/commands/app/delete/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/index.ts)_ ## `aio app:delete:action [ACTION-NAME]` -Delete existing actions - ``` Delete existing actions @@ -334,12 +428,10 @@ ALIASES $ aio app:delete:actions ``` -_See code: [src/commands/app/delete/action.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/action.js)_ +_See code: [src/commands/app/delete/action.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/action.ts)_ ## `aio app:delete:ci` -Delete existing CI files - ``` Delete existing CI files @@ -353,12 +445,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/delete/ci.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/ci.js)_ +_See code: [src/commands/app/delete/ci.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/ci.ts)_ ## `aio app:delete:event [EVENT-ACTION-NAME]` -Delete existing Adobe I/O Events actions - ``` Delete existing Adobe I/O Events actions @@ -378,12 +468,10 @@ ALIASES $ aio app:delete:events ``` -_See code: [src/commands/app/delete/event.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/event.js)_ +_See code: [src/commands/app/delete/event.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/event.ts)_ ## `aio app:delete:extension` -Add new extensions or a standalone application to the project - ``` Add new extensions or a standalone application to the project @@ -403,12 +491,10 @@ ALIASES $ aio app:delete:extensions ``` -_See code: [src/commands/app/delete/extension.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/extension.js)_ +_See code: [src/commands/app/delete/extension.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/extension.ts)_ ## `aio app:delete:service` -Delete Services in the current Workspace - ``` Delete Services in the current Workspace @@ -424,12 +510,10 @@ ALIASES $ aio app:delete:services ``` -_See code: [src/commands/app/delete/service.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/service.js)_ +_See code: [src/commands/app/delete/service.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/service.ts)_ ## `aio app:delete:web-assets` -Delete existing web assets - ``` Delete existing web assets @@ -443,12 +527,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/delete/web-assets.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/web-assets.js)_ +_See code: [src/commands/app/delete/web-assets.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/delete/web-assets.ts)_ ## `aio app:deploy` -Build and deploy an Adobe I/O App - ``` Build and deploy an Adobe I/O App @@ -459,54 +541,54 @@ USAGE $ aio app:deploy OPTIONS - -a, --action=action Deploy only a specific action, the flags can be specified multiple times, this will set - --no-publish + -a, --action=action Deploy only a specific action, the flags can be specified multiple times, this will set + --no-publish - -e, --extension=extension Deploy only a specific extension, the flags can be specified multiple times + -e, --extension=extension Deploy only a specific extension, the flags can be specified multiple times - -v, --verbose Verbose output + -v, --verbose Verbose output - --[no-]actions [default: true] Deploy actions if any + --[no-]actions [default: true] Deploy actions if any - --[no-]build [default: true] Run the build phase before deployment + --[no-]build [default: true] Run the build phase before deployment - --[no-]content-hash [default: true] Enable content hashing in browser code + --[no-]content-hash [default: true] Enable content hashing in browser code - --[no-]force-build [default: true] Force a build even if one already exists + --[no-]force-build [default: true] Force a build even if one already exists - --force-publish Force publish extension(s) to Exchange, delete previously published extension points + --force-publish Force publish extension(s) to Exchange, delete previously published extension points - --open Open the default web browser after a successful deploy, only valid if your app has a - front-end + --[no-]log-forwarding-update [default: true] Update log forwarding configuration on server - --[no-]publish [default: true] Publish extension(s) to Exchange + --open Open the default web browser after a successful deploy, only valid if your app has a + front-end - --skip-actions [deprecated] Please use --no-actions + --[no-]publish [default: true] Publish extension(s) to Exchange - --skip-build [deprecated] Please use --no-build + --skip-actions [deprecated] Please use --no-actions - --skip-deploy [deprecated] Please use 'aio app build' + --skip-build [deprecated] Please use --no-build - --skip-static [deprecated] Please use --no-web-assets + --skip-deploy [deprecated] Please use 'aio app build' - --skip-web-assets [deprecated] Please use --no-web-assets + --skip-static [deprecated] Please use --no-web-assets - --version Show version + --skip-web-assets [deprecated] Please use --no-web-assets + + --version Show version - --[no-]web-assets [default: true] Deploy web-assets if any + --[no-]web-assets [default: true] Deploy web-assets if any - --web-optimize [default: false] Enable optimization (minification) of web js/css/html + --web-optimize [default: false] Enable optimization (minification) of web js/css/html DESCRIPTION This will always force a rebuild unless --no-force-build is set. ``` -_See code: [src/commands/app/deploy.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/deploy.js)_ +_See code: [src/commands/app/deploy.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/deploy.ts)_ ## `aio app:get-url [ACTION]` -Get action URLs - ``` Get action URLs @@ -522,12 +604,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/get-url.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/get-url.js)_ +_See code: [src/commands/app/get-url.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/get-url.ts)_ ## `aio app:info` -Display settings/configuration in use by an Adobe I/O App - ``` Display settings/configuration in use by an Adobe I/O App @@ -544,12 +624,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/info.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/info.js)_ +_See code: [src/commands/app/info.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/info.ts)_ ## `aio app:init [PATH]` -Create a new Adobe I/O App - ``` Create a new Adobe I/O App @@ -582,12 +660,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/init.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/init.js)_ +_See code: [src/commands/app/init.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/init.ts)_ ## `aio app:list` -List components for Adobe I/O App - ``` List components for Adobe I/O App @@ -599,12 +675,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/list/index.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/list/index.js)_ +_See code: [src/commands/app/list/index.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/list/index.ts)_ ## `aio app:list:extension` -List implemented extensions - ``` List implemented extensions @@ -623,12 +697,10 @@ ALIASES $ aio app:list:extensions ``` -_See code: [src/commands/app/list/extension.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/list/extension.js)_ +_See code: [src/commands/app/list/extension.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/list/extension.ts)_ ## `aio app:list:extension-points` -List all extension points for the selected org - ``` List all extension points for the selected org @@ -647,12 +719,10 @@ ALIASES $ aio app:list:extension-points ``` -_See code: [src/commands/app/list/extension-points.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/list/extension-points.js)_ +_See code: [src/commands/app/list/extension-points.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/list/extension-points.ts)_ ## `aio app:logs` -Fetch logs for an Adobe I/O App - ``` Fetch logs for an Adobe I/O App @@ -671,12 +741,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/logs.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/logs.js)_ +_See code: [src/commands/app/logs.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/logs.ts)_ ## `aio app:run` -Run an Adobe I/O App - ``` Run an Adobe I/O App @@ -694,12 +762,10 @@ OPTIONS --version Show version ``` -_See code: [src/commands/app/run.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/run.js)_ +_See code: [src/commands/app/run.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/run.ts)_ ## `aio app:test` -Run tests for an Adobe I/O App - ``` Run tests for an Adobe I/O App If no flags are specified, by default only unit-tests are run. @@ -728,12 +794,10 @@ DESCRIPTION If the extension has a hook called 'test' in its 'ext.config.yaml', the script specified will be run instead. ``` -_See code: [src/commands/app/test.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/test.js)_ +_See code: [src/commands/app/test.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/test.ts)_ ## `aio app:undeploy` -Undeploys an Adobe I/O App - ``` Undeploys an Adobe I/O App @@ -754,12 +818,10 @@ OPTIONS --[no-]web-assets [default: true] Undeploy web-assets if any ``` -_See code: [src/commands/app/undeploy.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/undeploy.js)_ +_See code: [src/commands/app/undeploy.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/undeploy.ts)_ ## `aio app:use [CONFIG_FILE_PATH]` -Import an Adobe Developer Console configuration file. - ``` Import an Adobe Developer Console configuration file. @@ -816,5 +878,5 @@ DESCRIPTION page in https://console.adobe.io ``` -_See code: [src/commands/app/use.js](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/use.js)_ +_See code: [src/commands/app/use.ts](https://github.com/adobe/aio-cli-plugin-app/blob/8.4.0/src/commands/app/use.ts)_ diff --git a/src/commands/app/config/get/index.js b/src/commands/app/config/get/index.js new file mode 100644 index 00000000..c9e26cca --- /dev/null +++ b/src/commands/app/config/get/index.js @@ -0,0 +1,25 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const HHelp = require('@oclif/plugin-help').default +const BaseCommand = require('../../../../BaseCommand') + +class IndexCommand extends BaseCommand { + async run () { + const help = new HHelp(this.config) + help.showHelp(['app:config:get', '--help']) + } +} + +IndexCommand.description = 'Get app config' +IndexCommand.aliases = ['app:config:get'] + +module.exports = IndexCommand diff --git a/src/commands/app/config/get/log-forwarding.js b/src/commands/app/config/get/log-forwarding.js new file mode 100644 index 00000000..69b97bb3 --- /dev/null +++ b/src/commands/app/config/get/log-forwarding.js @@ -0,0 +1,46 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const BaseCommand = require('../../../../BaseCommand') +const LogForwarding = require('../../../../lib/log-forwarding') + +class LogForwardingCommand extends BaseCommand { + async run () { + const lf = await LogForwarding.init(this.getFullConfig().aio) + + const localConfig = lf.getLocalConfig() + const serverConfig = await lf.getServerConfig() + + if (!localConfig.isEqual(serverConfig)) { + this.log('Local and server log forwarding configuration is different') + this.log("Run either 'aio app:deploy' to update the server, " + + "or 'aio app:config:set:log-forwarding' to set new local and server configuration") + this.log('Local configuration:') + this.printConfig(localConfig) + this.log('\nServer configuration:') + } + this.printConfig(serverConfig) + } + + printConfig (config) { + if (config.isDefined()) { + this.log(`destination: ${config.getDestination()}`) + this.log('settings:', config.getSettings()) + } else { + this.log('Not defined') + } + } +} + +LogForwardingCommand.description = 'Get log forwarding destination configuration' +LogForwardingCommand.aliases = ['app:config:get:log-forwarding', 'app:config:get:lf'] + +module.exports = LogForwardingCommand diff --git a/src/commands/app/config/get/log-forwarding/errors.js b/src/commands/app/config/get/log-forwarding/errors.js new file mode 100644 index 00000000..510170d6 --- /dev/null +++ b/src/commands/app/config/get/log-forwarding/errors.js @@ -0,0 +1,46 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const BaseCommand = require('../../../../../BaseCommand') +const rtLib = require('@adobe/aio-lib-runtime') +const ora = require('ora') + +class ErrorsCommand extends BaseCommand { + async run () { + const spinner = ora() + const lf = await this.getLogForwarding() + spinner.start('Checking for errors...') + const res = await lf.getErrors() + const destinationMessage = res.configured_forwarder !== undefined + ? ` for the last configured destination '${res.configured_forwarder}'` + : '' + if (res.errors && res.errors.length > 0) { + spinner.succeed(`Log forwarding errors${destinationMessage}:\n` + res.errors.join('\n')) + } else { + spinner.succeed(`No log forwarding errors${destinationMessage}`) + } + } + + async getLogForwarding () { + const runtimeConfig = this.getFullConfig().aio.runtime + rtLib.utils.checkOpenWhiskCredentials({ ow: runtimeConfig }) + const rt = await rtLib.init({ + ...runtimeConfig, + api_key: runtimeConfig.auth + }) + return rt.logForwarding + } +} + +ErrorsCommand.description = 'Get log forwarding errors' +ErrorsCommand.aliases = ['app:config:get:log-forwarding:errors', 'app:config:get:lf:errors'] + +module.exports = ErrorsCommand diff --git a/src/commands/app/config/index.js b/src/commands/app/config/index.js new file mode 100644 index 00000000..5fb66716 --- /dev/null +++ b/src/commands/app/config/index.js @@ -0,0 +1,25 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const HHelp = require('@oclif/plugin-help').default +const BaseCommand = require('../../../BaseCommand') + +class IndexCommand extends BaseCommand { + async run () { + const help = new HHelp(this.config) + help.showHelp(['app:config', '--help']) + } +} + +IndexCommand.description = 'Manage app config' +IndexCommand.aliases = ['app:config', 'app:config'] + +module.exports = IndexCommand diff --git a/src/commands/app/config/set/index.js b/src/commands/app/config/set/index.js new file mode 100644 index 00000000..25801e31 --- /dev/null +++ b/src/commands/app/config/set/index.js @@ -0,0 +1,25 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const HHelp = require('@oclif/plugin-help').default +const BaseCommand = require('../../../../BaseCommand') + +class IndexCommand extends BaseCommand { + async run () { + const help = new HHelp(this.config) + help.showHelp(['app:config:set', '--help']) + } +} + +IndexCommand.description = 'Set app config' +IndexCommand.aliases = ['app:config:set'] + +module.exports = IndexCommand diff --git a/src/commands/app/config/set/log-forwarding.js b/src/commands/app/config/set/log-forwarding.js new file mode 100644 index 00000000..e6ccbd1d --- /dev/null +++ b/src/commands/app/config/set/log-forwarding.js @@ -0,0 +1,45 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const BaseCommand = require('../../../../BaseCommand') +const LogForwarding = require('../../../../lib/log-forwarding') + +class LogForwardingCommand extends BaseCommand { + async run () { + const lf = await LogForwarding.init(this.getFullConfig().aio) + + const destination = await this.promptDestination(lf.getSupportedDestinations()) + const destinationSettingsConfig = lf.getSettingsConfig(destination) + const settings = await this.prompt(destinationSettingsConfig) + const lfConfig = new LogForwarding.LogForwardingConfig(destination, settings) + + const res = await lf.updateServerConfig(lfConfig) + this.log(`Log forwarding is set to '${destination}'`) + + await lf.updateLocalConfig(lf.getConfigFromJson(res)) + this.log('Log forwarding settings are saved to the local configuration') + } + + async promptDestination (supportedDestinations) { + const responses = await this.prompt([{ + name: 'type', + message: 'select log forwarding destination', + type: 'list', + choices: supportedDestinations + }]) + return responses.type + } +} + +LogForwardingCommand.description = 'Set log forwarding destination configuration' +LogForwardingCommand.aliases = ['app:config:set:log-forwarding', 'app:config:set:lf'] + +module.exports = LogForwardingCommand diff --git a/src/commands/app/deploy.js b/src/commands/app/deploy.js index 98470a0f..42ce431b 100644 --- a/src/commands/app/deploy.js +++ b/src/commands/app/deploy.js @@ -21,6 +21,7 @@ const webLib = require('@adobe/aio-lib-web') const { flags } = require('@oclif/command') const { createWebExportFilter, runScript, buildExtensionPointPayloadWoMetadata, buildExcShellViewExtensionMetadata } = require('../../lib/app-helper') const rtLib = require('@adobe/aio-lib-runtime') +const LogForwarding = require('../../lib/log-forwarding') class Deploy extends BuildCommand { async run () { @@ -55,7 +56,29 @@ class Deploy extends BuildCommand { const spinner = ora() try { - // 1. deploy actions and web assets for each extension + const aioConfig = this.getFullConfig().aio + // 1. update log forwarding configuration + if (flags['log-forwarding-update'] && flags.actions) { + spinner.start('Updating log forwarding configuration') + try { + const lf = await LogForwarding.init(aioConfig) + if (lf.isLocalConfigChanged()) { + const lfConfig = lf.getLocalConfigWithSecrets() + if (lfConfig.isDefined()) { + await lf.updateServerConfig(lfConfig) + spinner.succeed(chalk.green(`Log forwarding is set to '${lfConfig.getDestination()}'`)) + } else { + spinner.fail(chalk.green('Log forwarding is not updated: no configuration is provided')) + } + } else { + spinner.fail(chalk.green('Log forwarding is not updated: configuration not changed since last update')) + } + } catch (error) { + spinner.fail(chalk.red('Log forwarding is not updated.')) + throw error + } + } + // 2. deploy actions and web assets for each extension // Possible improvements: // - parallelize // - break into smaller pieces deploy, allowing to first deploy all actions then all web assets @@ -64,9 +87,9 @@ class Deploy extends BuildCommand { const v = values[i] await this.deploySingleConfig(k, v, flags, spinner) } - // 2. deploy extension manifest + + // 3. deploy extension manifest if (flags.publish) { - const aioConfig = this.getFullConfig().aio const payload = await this.publishExtensionPoints(libConsoleCLI, deployConfigs, aioConfig, flags['force-publish']) this.log(chalk.blue(chalk.bold(`New Extension Point(s) in Workspace '${aioConfig.project.workspace.name}': '${Object.keys(payload.endpoints)}'`))) } else { @@ -302,6 +325,11 @@ Deploy.flags = { 'web-optimize': flags.boolean({ description: '[default: false] Enable optimization (minification) of web js/css/html', default: false + }), + 'log-forwarding-update': flags.boolean({ + description: '[default: true] Update log forwarding configuration on server', + default: true, + allowNo: true }) } diff --git a/src/commands/app/logs.js b/src/commands/app/logs.js index 35bed366..c873e635 100644 --- a/src/commands/app/logs.js +++ b/src/commands/app/logs.js @@ -11,10 +11,10 @@ governing permissions and limitations under the License. */ const { flags } = require('@oclif/command') -// const { cli } = require('cli-ux') const BaseCommand = require('../../BaseCommand') const { wrapError } = require('../../lib/app-helper') const rtLib = require('@adobe/aio-lib-runtime') +const LogForwarding = require('../../lib/log-forwarding') class Logs extends BaseCommand { _processEachAction (fullConfig, processFn) { @@ -40,6 +40,15 @@ class Logs extends BaseCommand { throw new Error('There are no backend implementations for this project folder.') } + const lf = await LogForwarding.init(fullConfig.aio) + const serverConfig = await lf.getServerConfig() + const logForwardingDestination = serverConfig.getDestination() + if (logForwardingDestination !== 'adobe_io_runtime') { + this.log(`Namespace is configured with custom log forwarding destination: '${logForwardingDestination}'. ` + + 'Please use corresponding logging platform to view logs.') + return + } + if (flags.limit < 1) { this.log('--limit should be > 0, using --limit=1') flags.limit = 1 diff --git a/src/lib/log-forwarding.js b/src/lib/log-forwarding.js new file mode 100644 index 00000000..c8a20449 --- /dev/null +++ b/src/lib/log-forwarding.js @@ -0,0 +1,259 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const rtLib = require('@adobe/aio-lib-runtime') +const { writeAio, writeEnv } = require('./import') +const crypto = require('crypto') +const fs = require('fs-extra') +const path = require('path') + +const SECRET_FIELD_TYPE = 'password' +const CHECKSUM_DIR = 'dist' +const CHECKSUM_FILE = 'log-forwarding-config.sha256' +const IGNORED_REMOTE_SETTINGS = ['updated_at'] + +class LogForwarding { + constructor (aioConfig) { + this.aioConfig = aioConfig + } + + async init () { + rtLib.utils.checkOpenWhiskCredentials({ ow: this.aioConfig.runtime }) + this.logForwarding = await getRTLogForwarding(this.aioConfig.runtime) + return this + } + + getLocalConfig () { + const config = this.aioConfig.project.workspace.log_forwarding + try { + return this.getConfigFromJson(config) + } catch (e) { + throw new Error('Incorrect local log forwarding configuration. ' + e.message) + } + } + + getLocalConfigWithSecrets () { + const config = this.getLocalConfig() + const destination = config.getDestination() + const settings = config.getSettings() + if (config.isDefined()) { + const destinationSettings = this.logForwarding.getDestinationSettings(destination) + const missingSecrets = [] + destinationSettings.forEach(e => { + if (e.type === SECRET_FIELD_TYPE) { + const secretVarName = getSecretVarName(destination, e.name) + if (process.env[secretVarName] !== undefined) { + settings[e.name] = process.env[secretVarName] + } else { + missingSecrets.push(secretVarName) + } + } + }) + if (missingSecrets.length > 0) { + throw new Error('Required secrets are missing in environment variables: ' + missingSecrets.join(', ') + '. ' + + 'Make sure these variables are set in .env file') + } + } + return new LogForwardingConfig(destination, settings) + } + + async getServerConfig () { + try { + return this.getConfigFromJson(await this.logForwarding.get()) + } catch (e) { + throw new Error('Incorrect log forwarding configuration on server. ' + e.message) + } + } + + /** + * Convert JSON config to Log Forwarding object + * + * @param {object} configJson Config in JSON format + * @returns {LogForwardingConfig} Config + */ + getConfigFromJson (configJson) { + let destination + let settings + + if (configJson !== undefined && configJson !== null && !Array.isArray(configJson) && typeof configJson === 'object') { + const destinations = Object.keys(configJson) + if (destinations.length === 1) { + destination = destinations[0] + settings = configJson[destination] + } else { + throw new Error(`Configuration has ${destinations.length} destinations. Exactly one must be defined.`) + } + } + return new LogForwardingConfig(destination, settings) + } + + getSupportedDestinations () { + return this.logForwarding.getSupportedDestinations() + } + + getSettingsConfig (destination) { + return this.logForwarding.getDestinationSettings(destination) + } + + async updateLocalConfig (lfConfig) { + const destination = lfConfig.getDestination() + const destinationSettings = this.logForwarding.getDestinationSettings(destination) + const projectConfig = { + project: this.aioConfig.project + } + + const nonSecretSettings = {} + const secretSettings = {} + + const settings = lfConfig.getSettings() + Object.keys(settings) + .filter(e => !IGNORED_REMOTE_SETTINGS.includes(e)) + .forEach(k => { + const destFieldSettings = destinationSettings.find(i => i.name === k) + if (destFieldSettings.type === SECRET_FIELD_TYPE) { + secretSettings[getSecretVarName(destination, k)] = settings[k] + } else { + nonSecretSettings[k] = settings[k] + } + }) + + projectConfig.project.workspace.log_forwarding = { + [destination]: nonSecretSettings + } + const interactive = false + const merge = true + await writeAio(projectConfig, '', { interactive, merge }) + await writeEnv({}, '', { interactive, merge }, secretSettings) + } + + isLocalConfigChanged () { + if (fs.pathExistsSync(path.join(CHECKSUM_DIR, CHECKSUM_FILE))) { + const oldChecksum = fs.readFileSync(path.join(CHECKSUM_DIR, CHECKSUM_FILE)).toString() + const config = this.getLocalConfigWithSecrets() + const newChecksum = getChecksum(config) + return oldChecksum !== newChecksum + } else { + return true + } + } + + async updateServerConfig (lfConfig) { + const res = await this.logForwarding.setDestination(lfConfig.getDestination(), lfConfig.getSettings()) + const checksum = getChecksum(lfConfig) + fs.ensureDirSync(CHECKSUM_DIR) + fs.writeFile(path.join(CHECKSUM_DIR, CHECKSUM_FILE), checksum, { flags: 'w' }) + return res + } +} + +class LogForwardingConfig { + constructor (destination, settings) { + this.destination = destination + this.settings = settings + } + + getDestination () { + return this.destination + } + + getSettings () { + return this.settings + } + + isDefined () { + return this.destination !== undefined + } + + isDefault () { + return !this.isDefined() || this.getDestination() === 'adobe_io_runtime' + } + + isEqual (config) { + return (this.isDefault() && config.isDefault()) || + (this.destination === config.getDestination() && shallowEqual(this.settings, config.settings)) + } +} + +/** + * Init Log Forwarding + * + * @param {object} aioConfig aio Config + * @returns {Promise} Log Forwarding + */ +async function init (aioConfig) { + const lf = new LogForwarding(aioConfig) + return await lf.init() +} + +/** + * Get Runtime Log Forwarding + * + * @param {object} rtConfig Runtime config + * @returns {Promise} Log Forwarding + */ +async function getRTLogForwarding (rtConfig) { + const rt = await rtLib.init({ + ...rtConfig, + api_key: rtConfig.auth + }) + return rt.logForwarding +} + +/** + * Compare to log forwarding configs + * + * @param {LogForwardingConfig} config1 Config + * @param {LogForwardingConfig} config2 Config + * @returns {boolean} Are configs equal + */ +function shallowEqual (config1, config2) { + // updated_at exists on server only and does not impact actual configuration + const keys1 = Object.keys(config1).filter(e => !IGNORED_REMOTE_SETTINGS.includes(e)) + const keys2 = Object.keys(config2).filter(e => !IGNORED_REMOTE_SETTINGS.includes(e)) + if (keys1.length !== keys2.length) { + return false + } + for (const key of keys1) { + if (config1[key] !== config2[key]) { + return false + } + } + return true +} + +/** + * Get secret variable name for the given destination and settings field + * + * @param {string} destination Destination + * @param {string} fieldName Field name + * @returns {string} Variable name + */ +function getSecretVarName (destination, fieldName) { + return destination.toUpperCase() + '__' + fieldName.toUpperCase() +} + +/** + * Generate checksum for the config + * + * @param {LogForwardingConfig} config Config + * @returns {string} Checksum + */ +function getChecksum (config) { + return crypto.createHash('sha256') + .update(JSON.stringify(config)) + .digest('hex') +} + +module.exports = { + init, + LogForwardingConfig +} diff --git a/test/__mocks__/@adobe/aio-lib-runtime.js b/test/__mocks__/@adobe/aio-lib-runtime.js index 1121378b..c45789e0 100644 --- a/test/__mocks__/@adobe/aio-lib-runtime.js +++ b/test/__mocks__/@adobe/aio-lib-runtime.js @@ -25,6 +25,12 @@ const cleanRtLibInstance = { triggers: { list: jest.fn(() => '') }, + logForwarding: { + get: jest.fn(), + setAdobeIoRuntime: jest.fn(), + setAzureLogAnalytics: jest.fn(), + setSplunkHec: jest.fn() + }, feeds: {}, routes: {} } diff --git a/test/commands/app/config/get/index.test.js b/test/commands/app/config/get/index.test.js new file mode 100644 index 00000000..13821c44 --- /dev/null +++ b/test/commands/app/config/get/index.test.js @@ -0,0 +1,22 @@ +/* +Copyright 2019 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const TheCommand = require('../../../../../src/commands/app/config/get/index') +const HHelp = require('@oclif/plugin-help').default + +test('returns help file for app:config:get command', () => { + const command = new TheCommand([]) + const spy = jest.spyOn(HHelp.prototype, 'showHelp').mockReturnValue(true) + return command.run().then(() => { + expect(spy).toHaveBeenCalledWith(['app:config:get', '--help']) + }) +}) diff --git a/test/commands/app/config/get/log-forwarding.test.js b/test/commands/app/config/get/log-forwarding.test.js new file mode 100644 index 00000000..a4b9269f --- /dev/null +++ b/test/commands/app/config/get/log-forwarding.test.js @@ -0,0 +1,180 @@ +/* +Copyright 2019 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { stdout } = require('stdout-stderr') +const TheCommand = require('../../../../../src/commands/app/config/get/log-forwarding.js') +const LogForwarding = require('../../../../../src/lib/log-forwarding') + +jest.mock('../../../../../src/lib/log-forwarding', () => { + const orig = jest.requireActual('../../../../../src/lib/log-forwarding') + return { + ...orig, + init: jest.fn() + } +}) + +let command, lf +beforeEach(async () => { + command = new TheCommand([]) + command.appConfig = { + aio: { + runtime: { + namespace: 'fake_ns', + auth: 'fake:auth', + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + package: 'sample-app-1.0.0' + } + } + } + lf = { + getLocalConfig: jest.fn(), + getServerConfig: jest.fn() + } + + LogForwarding.init.mockResolvedValue(lf) +}) + +test('get log forwarding settings (local and server are the same)', async () => { + return new Promise(resolve => { + const localConfig = new LogForwarding.LogForwardingConfig( + 'destination', + { field_one: 'value one', field_two: 'value two' } + ) + const serverConfig = new LogForwarding.LogForwardingConfig( + 'destination', + { field_one: 'value one', field_two: 'value two', updated_at: '2021-08-27T14:40:06.000+00:00' } + ) + + lf.getLocalConfig.mockReturnValue(localConfig) + lf.getServerConfig.mockResolvedValue(serverConfig) + + return command.run() + .then(() => { + expect(stdout.output).toEqual('destination: destination\n' + + 'settings: {\n' + + " field_one: 'value one',\n" + + " field_two: 'value two',\n" + + " updated_at: '2021-08-27T14:40:06.000+00:00'\n" + + '}\n') + resolve() + }) + }) +}) + +test('get log forwarding settings (no local and server config)', async () => { + return new Promise(resolve => { + const localConfig = new LogForwarding.LogForwardingConfig() + const serverConfig = new LogForwarding.LogForwardingConfig() + + lf.getLocalConfig.mockReturnValue(localConfig) + lf.getServerConfig.mockResolvedValue(serverConfig) + + return command.run() + .then(() => { + expect(stdout.output).toEqual('Not defined\n') + resolve() + }) + }) +}) + +test('get log forwarding settings (local and server are different)', async () => { + return new Promise(resolve => { + const localConfig = new LogForwarding.LogForwardingConfig( + 'destination_one', + { field_one: 'value one', field_two: 'value two' } + ) + const serverConfig = new LogForwarding.LogForwardingConfig( + 'destination_two', + { field_one: 'value two', field_three: 'value three', updated_at: '2021-08-27T14:40:06.000+00:00' } + ) + + lf.getLocalConfig.mockReturnValue(localConfig) + lf.getServerConfig.mockResolvedValue(serverConfig) + + return command.run() + .then(() => { + expect(stdout.output).toEqual('Local and server log forwarding configuration is different\n' + + "Run either 'aio app:deploy' to update the server, or 'aio app:config:set:log-forwarding' to set new local and server configuration\n" + + 'Local configuration:\n' + + 'destination: destination_one\n' + + "settings: { field_one: 'value one', field_two: 'value two' }\n\n" + + 'Server configuration:\n' + + 'destination: destination_two\n' + + 'settings: {\n' + + " field_one: 'value two',\n" + + " field_three: 'value three',\n" + + " updated_at: '2021-08-27T14:40:06.000+00:00'\n" + + '}\n') + resolve() + }) + }) +}) + +test('get log forwarding settings (no local config)', async () => { + return new Promise(resolve => { + const localConfig = new LogForwarding.LogForwardingConfig() + const serverConfig = new LogForwarding.LogForwardingConfig( + 'destination_two', + { field_one: 'value two', field_three: 'value three', updated_at: '2021-08-27T14:40:06.000+00:00' } + ) + + lf.getLocalConfig.mockReturnValue(localConfig) + lf.getServerConfig.mockResolvedValue(serverConfig) + + return command.run() + .then(() => { + expect(stdout.output).toEqual('Local and server log forwarding configuration is different\n' + + "Run either 'aio app:deploy' to update the server, or 'aio app:config:set:log-forwarding' to set new local and server configuration\n" + + 'Local configuration:\n' + + 'Not defined\n\n' + + 'Server configuration:\n' + + 'destination: destination_two\n' + + 'settings: {\n' + + " field_one: 'value two',\n" + + " field_three: 'value three',\n" + + " updated_at: '2021-08-27T14:40:06.000+00:00'\n" + + '}\n') + resolve() + }) + }) +}) + +test('get log forwarding settings (no server config)', async () => { + return new Promise(resolve => { + const localConfig = new LogForwarding.LogForwardingConfig( + 'destination_one', + { field_one: 'value one', field_two: 'value two' } + ) + const serverConfig = new LogForwarding.LogForwardingConfig() + + lf.getLocalConfig.mockReturnValue(localConfig) + lf.getServerConfig.mockResolvedValue(serverConfig) + + return command.run() + .then(() => { + expect(stdout.output).toEqual('Local and server log forwarding configuration is different\n' + + "Run either 'aio app:deploy' to update the server, or 'aio app:config:set:log-forwarding' to set new local and server configuration\n" + + 'Local configuration:\n' + + 'destination: destination_one\n' + + "settings: { field_one: 'value one', field_two: 'value two' }\n\n" + + 'Server configuration:\n' + + 'Not defined\n') + resolve() + }) + }) +}) + +test('failed to get log forwarding settings', async () => { + lf.getServerConfig.mockRejectedValue(new Error('mocked error')) + await expect(command.run()).rejects.toThrow('mocked error') +}) diff --git a/test/commands/app/config/get/log-forwarding/errors.test.js b/test/commands/app/config/get/log-forwarding/errors.test.js new file mode 100644 index 00000000..304f5555 --- /dev/null +++ b/test/commands/app/config/get/log-forwarding/errors.test.js @@ -0,0 +1,108 @@ +/* +Copyright 2019 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const TheCommand = require('../../../../../../src/commands/app/config/get/log-forwarding/errors') +const RuntimeLib = require('@adobe/aio-lib-runtime') +const ora = require('ora') + +jest.mock('ora') + +let command, rtLib, spinner +beforeEach(async () => { + command = new TheCommand([]) + command.appConfig = { + aio: { + runtime: { + namespace: 'fake_ns', + auth: 'fake:auth', + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + package: 'sample-app-1.0.0' + } + } + } + rtLib = await RuntimeLib.init({ apihost: 'https://adobeioruntime.net', api_key: 'fake:auth' }) + RuntimeLib.utils.checkOpenWhiskCredentials = jest.fn() + rtLib.logForwarding.getErrors = jest.fn() + spinner = ora() +}) + +test('app:config:get:log-forwarding:errors command', async () => { + return new Promise(resolve => { + rtLib.logForwarding.getErrors.mockResolvedValue({ + configured_forwarder: 'destination', + errors: [ + 'error 1', + 'error 2' + ] + }) + + return command.run() + .then(() => { + expect(spinner.succeed) + .toBeCalledWith("Log forwarding errors for the last configured destination 'destination':\nerror 1\nerror 2") + resolve() + }) + }) +}) + +test('app:config:get:log-forwarding:errors command - no destination returned from the server', async () => { + return new Promise(resolve => { + rtLib.logForwarding.getErrors.mockResolvedValue({ + errors: [ + 'error 1', + 'error 2' + ] + }) + + return command.run() + .then(() => { + expect(spinner.succeed).toBeCalledWith('Log forwarding errors:\nerror 1\nerror 2') + resolve() + }) + }) +}) + +test('app:config:get:log-forwarding:errors command - no errors', async () => { + return new Promise(resolve => { + rtLib.logForwarding.getErrors.mockResolvedValue({ + configured_forwarder: 'destination', + errors: [] + }) + + return command.run() + .then(() => { + expect(spinner.succeed) + .toBeCalledWith("No log forwarding errors for the last configured destination 'destination'") + resolve() + }) + }) +}) + +test('app:config:get:log-forwarding:errors command - no errors and no destination returned from the server', async () => { + return new Promise(resolve => { + rtLib.logForwarding.getErrors.mockResolvedValue({ + errors: [] + }) + + return command.run() + .then(() => { + expect(spinner.succeed).toBeCalledWith('No log forwarding errors') + resolve() + }) + }) +}) + +test('app:config:get:log-forwarding:errors command - failed response from server', async () => { + rtLib.logForwarding.getErrors.mockRejectedValue(new Error('mocked error')) + await expect(command.run()).rejects.toThrow('mocked error') +}) diff --git a/test/commands/app/config/index.test.js b/test/commands/app/config/index.test.js new file mode 100644 index 00000000..c427c852 --- /dev/null +++ b/test/commands/app/config/index.test.js @@ -0,0 +1,22 @@ +/* +Copyright 2019 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const TheCommand = require('../../../../src/commands/app/config/index.js') +const HHelp = require('@oclif/plugin-help').default + +test('returns help file for app:config command', () => { + const command = new TheCommand([]) + const spy = jest.spyOn(HHelp.prototype, 'showHelp').mockReturnValue(true) + return command.run().then(() => { + expect(spy).toHaveBeenCalledWith(['app:config', '--help']) + }) +}) diff --git a/test/commands/app/config/set/index.test.js b/test/commands/app/config/set/index.test.js new file mode 100644 index 00000000..c4b10ec1 --- /dev/null +++ b/test/commands/app/config/set/index.test.js @@ -0,0 +1,22 @@ +/* +Copyright 2019 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const TheCommand = require('../../../../../src/commands/app/config/set/index.js') +const HHelp = require('@oclif/plugin-help').default + +test('returns help file for app:config:set command', () => { + const command = new TheCommand([]) + const spy = jest.spyOn(HHelp.prototype, 'showHelp').mockReturnValue(true) + return command.run().then(() => { + expect(spy).toHaveBeenCalledWith(['app:config:set', '--help']) + }) +}) diff --git a/test/commands/app/config/set/log-forwarding.test.js b/test/commands/app/config/set/log-forwarding.test.js new file mode 100644 index 00000000..0b5a8095 --- /dev/null +++ b/test/commands/app/config/set/log-forwarding.test.js @@ -0,0 +1,99 @@ +/* +Copyright 2019 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { stdout } = require('stdout-stderr') +const TheCommand = require('../../../../../src/commands/app/config/set/log-forwarding.js') +const LogForwarding = require('../../../../../src/lib/log-forwarding') + +jest.mock('../../../../../src/lib/log-forwarding', () => { + const orig = jest.requireActual('../../../../../src/lib/log-forwarding') + return { + ...orig, + init: jest.fn() + } +}) + +let command, lf +beforeEach(async () => { + command = new TheCommand([]) + command.appConfig = { + aio: { + runtime: { + namespace: 'fake_ns', + auth: 'fake:auth', + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + package: 'sample-app-1.0.0' + } + } + } + command.prompt = jest.fn() + + lf = { + getSupportedDestinations: jest.fn().mockReturnValue([{ value: 'destination', name: 'Destination' }]), + getSettingsConfig: jest.fn().mockReturnValue({ key: 'value' }), + updateServerConfig: jest.fn(), + updateLocalConfig: jest.fn(), + getConfigFromJson: jest.fn() + } + LogForwarding.init.mockResolvedValue(lf) +}) + +test('set log forwarding destination', async () => { + return new Promise(resolve => { + const destination = 'destination' + const input = { + field_one: 'val_one', + field_two: 'val_two' + } + command.prompt.mockResolvedValueOnce({ type: destination }) + command.prompt.mockResolvedValueOnce(input) + const serverSanitizedSettings = { + field_one: 'val_one', + field_two: 'val_two sanitized' + } + const setCall = jest.fn().mockResolvedValue({ + destination: serverSanitizedSettings + }) + const localSetCall = jest.fn() + lf.updateServerConfig = setCall + lf.updateLocalConfig = localSetCall + lf.getConfigFromJson.mockReturnValue(new LogForwarding.LogForwardingConfig(destination, serverSanitizedSettings)) + return command.run() + .then(() => { + expect(command.prompt).toHaveBeenNthCalledWith(1, [{ + name: 'type', + message: 'select log forwarding destination', + type: 'list', + choices: [{ name: 'Destination', value: 'destination' }] + }]) + expect(stdout.output).toMatch(`Log forwarding is set to '${destination}'\nLog forwarding settings are saved to the local configuration`) + expect(setCall).toBeCalledTimes(1) + expect(setCall).toHaveBeenCalledWith(new LogForwarding.LogForwardingConfig(destination, input)) + expect(localSetCall).toBeCalledTimes(1) + expect(localSetCall).toHaveBeenCalledWith(new LogForwarding.LogForwardingConfig(destination, serverSanitizedSettings)) + resolve() + }) + }) +}) + +test('failed to set log forwarding settings', async () => { + const destination = 'destination' + const input = { + field_one: 'val_one', + field_two: 'val_two' + } + command.prompt.mockResolvedValueOnce({ type: destination }) + lf.updateServerConfig = jest.fn().mockRejectedValue(new Error(`mocked error for ${destination}`)) + command.prompt.mockResolvedValueOnce(input) + await expect(command.run()).rejects.toThrow(`mocked error for ${destination}`) +}) diff --git a/test/commands/app/deploy.test.js b/test/commands/app/deploy.test.js index 725ce124..13371dc5 100644 --- a/test/commands/app/deploy.test.js +++ b/test/commands/app/deploy.test.js @@ -40,6 +40,15 @@ const mockConfigData = { jest.mock('cli-ux') const { cli } = require('cli-ux') +jest.mock('../../../src/lib/log-forwarding', () => { + const orig = jest.requireActual('../../../src/lib/log-forwarding') + return { + ...orig, + init: jest.fn() + } +}) +const LogForwarding = require('../../../src/lib/log-forwarding') + const createWebExportAnnotation = (value) => ({ body: { annotations: [ @@ -99,6 +108,12 @@ const mockLibConsoleCLI = { updateExtensionPointsWithoutOverwrites: jest.fn() } +const mockLogForwarding = { + isLocalConfigChanged: jest.fn(), + getLocalConfigWithSecrets: jest.fn(), + updateServerConfig: jest.fn() +} + afterAll(() => { jest.restoreAllMocks() }) @@ -109,11 +124,16 @@ beforeEach(() => { helpers.buildExtensionPointPayloadWoMetadata.mockReset() helpers.buildExcShellViewExtensionMetadata.mockReset() helpers.createWebExportFilter.mockReset() + mockLogForwarding.isLocalConfigChanged.mockReset() + mockLogForwarding.getLocalConfigWithSecrets.mockReset() + mockLogForwarding.updateServerConfig.mockReset() jest.restoreAllMocks() helpers.wrapError.mockImplementation(msg => msg) helpers.createWebExportFilter.mockImplementation(filterValue => helpersActual.createWebExportFilter(filterValue)) + + LogForwarding.init.mockResolvedValue(mockLogForwarding) }) test('exports', async () => { @@ -210,10 +230,22 @@ describe('run', () => { command.buildOneExt = jest.fn() command.getAppExtConfigs = jest.fn() command.getLibConsoleCLI = jest.fn(() => mockLibConsoleCLI) - command.getFullConfig = jest.fn() + command.getFullConfig = jest.fn().mockReturnValue({ + aio: { + project: { + workspace: { + name: 'foo' + } + } + } + }) mockRuntimeLib.deployActions.mockResolvedValue({ actions: [] }) mockWebLib.bundle.mockResolvedValue({ run: mockBundleFunc }) + + mockLogForwarding.isLocalConfigChanged.mockReturnValue(true) + const config = new LogForwarding.LogForwardingConfig('destination', { field: 'value' }) + mockLogForwarding.getLocalConfigWithSecrets.mockReturnValue(config) }) afterEach(() => { @@ -448,6 +480,19 @@ describe('run', () => { expect(command.buildOneExt).toHaveBeenCalledWith('application', appConfig.application, expect.objectContaining({ 'force-build': false }), expect.anything()) // force-build is true by default for build cmd }) + test.each([ + [['--no-log-forwarding-update']], + [['--no-actions']], + [['--no-log-forwarding-update', '--no-actions']] + ])('no log forwarding update due to %s arg(s) specified', async (args) => { + const appConfig = createAppConfig(command.appConfig) + command.getAppExtConfigs.mockReturnValueOnce(appConfig) + command.argv = args + await command.run() + expect(command.error).toHaveBeenCalledTimes(0) + expect(LogForwarding.init).toHaveBeenCalledTimes(0) + }) + test('deploy should show ui url', async () => { command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) mockWebLib.deployWeb.mockResolvedValue('https://example.com') @@ -550,6 +595,20 @@ describe('run', () => { expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) }) + test('should fail if log forwarding config is invalid', async () => { + command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) + const error = new Error('mock failure') + mockLogForwarding.getLocalConfigWithSecrets.mockImplementation(() => { throw error }) + await expect(command.run()).rejects.toEqual(error) + }) + + test('should fail if log forwarding update fails', async () => { + command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) + const error = new Error('mock failure') + mockLogForwarding.updateServerConfig.mockImplementation(() => { throw error }) + await expect(command.run()).rejects.toEqual(error) + }) + test('spinner should be called for progress logs on deployWeb call , with verbose', async () => { command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) mockRuntimeLib.deployActions.mockResolvedValue({ actions: [] }) @@ -827,4 +886,26 @@ describe('run', () => { expect(scriptSequence[2]).toEqual('deploy-static') expect(scriptSequence[3]).toEqual('post-app-deploy') }) + + test('should update log forwarding on server when local config is defined', async () => { + command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) + const expectedConfig = new LogForwarding.LogForwardingConfig('destination', { field: 'value' }) + await command.run() + expect(mockLogForwarding.updateServerConfig).toBeCalledWith(expectedConfig) + }) + + test('log forwarding is not updated on server when local config is absent', async () => { + command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) + const config = new LogForwarding.LogForwardingConfig() + mockLogForwarding.getLocalConfigWithSecrets.mockReturnValue(config) + await command.run() + expect(mockLogForwarding.updateServerConfig).toBeCalledTimes(0) + }) + + test('log forwarding is not updated on server when local config not changed', async () => { + command.getAppExtConfigs.mockReturnValueOnce(createAppConfig(command.appConfig)) + mockLogForwarding.isLocalConfigChanged.mockReturnValue(false) + await command.run() + expect(mockLogForwarding.updateServerConfig).toBeCalledTimes(0) + }) }) diff --git a/test/commands/app/logs.test.js b/test/commands/app/logs.test.js index 6df63db6..79da2b72 100644 --- a/test/commands/app/logs.test.js +++ b/test/commands/app/logs.test.js @@ -13,10 +13,18 @@ governing permissions and limitations under the License. const TheCommand = require('../../../src/commands/app/logs') const BaseCommand = require('../../../src/BaseCommand') const dataMocks = require('../../data-mocks/config-loader') +const LogForwarding = require('../../../src/lib/log-forwarding') + +jest.mock('../../../src/lib/log-forwarding', () => { + const orig = jest.requireActual('../../../src/lib/log-forwarding') + return { + ...orig, + init: jest.fn() + } +}) const createFullConfig = (aioConfig = {}, appFixtureName = 'legacy-app') => { - const appConfig = dataMocks(appFixtureName, aioConfig) - return appConfig + return dataMocks(appFixtureName, aioConfig) } const mockFS = require('fs-extra') @@ -51,7 +59,7 @@ describe('interface', () => { }) describe('run', () => { - let command + let command, logForwarding const owConfig = () => Object.values(command.appConfig.all)[0].ow // every extension has the same 'ow' package @@ -66,6 +74,11 @@ describe('run', () => { command.log = jest.fn() command.getFullConfig = jest.fn() command.getFullConfig.mockReturnValue(command.appConfig) + logForwarding = { + getServerConfig: jest.fn() + .mockResolvedValue(new LogForwarding.LogForwardingConfig('adobe_io_runtime', {})) + } + LogForwarding.init.mockResolvedValue(logForwarding) }) test('no flags, sets limit to 1', async () => { @@ -78,6 +91,16 @@ describe('run', () => { expect(command.error).not.toHaveBeenCalled() }) + test('no flags, custom log forwarding', async () => { + mockFS.existsSync.mockReturnValue(true) + logForwarding.getServerConfig.mockResolvedValue( + new LogForwarding.LogForwardingConfig('custom_destination', {}) + ) + + await command.run() + expect(printActionLogs).not.toHaveBeenCalled() + }) + test('--limit < 1, sets limit to 1', async () => { mockFS.existsSync.mockReturnValue(true) command.argv = ['--limit', '-1'] diff --git a/test/commands/lib/log-forwarding.test.js b/test/commands/lib/log-forwarding.test.js new file mode 100644 index 00000000..139f397c --- /dev/null +++ b/test/commands/lib/log-forwarding.test.js @@ -0,0 +1,369 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const RuntimeLib = require('@adobe/aio-lib-runtime') +const LogForwarding = require('../../../src/lib/log-forwarding') +const { writeAio, writeEnv } = require('../../../src/lib/import') +const fs = require('fs-extra') +const path = require('path') + +jest.mock('../../../src/lib/import', () => { + return { + writeAio: jest.fn(), + writeEnv: jest.fn() + } +}) +jest.mock('fs-extra') + +const rtConfig = { + namespace: 'fake_ns', + auth: 'fake:auth', + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + package: 'sample-app-1.0.0' +} + +const LF_CONFIGPATH = path.join('dist', 'log-forwarding-config.sha256') + +let lf, rtLib + +beforeEach(async () => { + rtLib = await RuntimeLib.init({ apihost: 'https://adobeioruntime.net', api_key: 'fakekey' }) + RuntimeLib.utils.checkOpenWhiskCredentials = jest.fn() + rtLib.logForwarding.getDestinationSettings = jest.fn().mockReturnValue([ + { + name: 'field', + message: 'Field' + }, + { + name: 'secret_field', + message: 'Secret', + type: 'password' + } + ]) +}) + +test('getLocalConfig (adobe_io_runtime)', async () => { + const aioConfig = { + project: { + workspace: { + log_forwarding: { + adobe_io_runtime: {} + } + } + }, + runtime: rtConfig + } + lf = await LogForwarding.init(aioConfig) + const expected = new LogForwarding.LogForwardingConfig('adobe_io_runtime', {}) + expect(lf.getLocalConfig()).toEqual(expected) + expect(lf.getLocalConfig().isDefault()).toEqual(true) +}) + +describe('with local log forwarding config', () => { + beforeEach(async () => { + const aioConfig = { + project: { + workspace: { + log_forwarding: { + destination: { + field: 'value' + } + } + } + }, + runtime: rtConfig + } + lf = await LogForwarding.init(aioConfig) + }) + + test('getLocalConfig (custom)', async () => { + const expected = new LogForwarding.LogForwardingConfig( + 'destination', + { + field: 'value' + }) + expect(lf.getLocalConfig()).toEqual(expected) + }) + + test('getLocalConfigWithSecrets (fails due to not defined secrets in env vars)', async () => { + expect(() => lf.getLocalConfigWithSecrets()).toThrow('Required secrets are missing in environment variables: DESTINATION__SECRET_FIELD') + }) +}) + +describe('with secrets in env vars', () => { + const originalEnv = process.env + beforeEach(async () => { + jest.resetModules() + const aioConfig = { + project: { + workspace: { + log_forwarding: { + destination: { + field: 'value' + } + } + } + }, + runtime: rtConfig + } + process.env = { + ...originalEnv, + DESTINATION__SECRET_FIELD: 'secret' + } + lf = await LogForwarding.init(aioConfig) + }) + + afterEach(() => { + process.env = originalEnv + }) + + test('getLocalConfigWithSecrets', async () => { + const expected = new LogForwarding.LogForwardingConfig( + 'destination', + { + field: 'value', + secret_field: 'secret' + }) + expect(lf.getLocalConfigWithSecrets()).toEqual(expected) + }) + + test('isLocalConfigChanged (not changed)', async () => { + fs.pathExistsSync.mockReturnValue(true) + fs.readFileSync.mockReturnValue('e35c04da5060b3aa1406a907ca149f5d974d5ada308163046080c02c98796cf9') + expect(lf.isLocalConfigChanged()).toEqual(false) + expect(fs.pathExistsSync).toHaveBeenCalledWith(LF_CONFIGPATH) + }) + + test('isLocalConfigChanged (changed)', async () => { + fs.pathExistsSync.mockReturnValue(true) + fs.readFileSync.mockReturnValue('outdated-checksum') + expect(lf.isLocalConfigChanged()).toEqual(true) + expect(fs.pathExistsSync).toHaveBeenCalledWith(LF_CONFIGPATH) + }) +}) + +describe('absent local log forwarding config', () => { + beforeEach(async () => { + const aioConfig = { + project: { + workspace: {} + }, + runtime: rtConfig + } + lf = await LogForwarding.init(aioConfig) + }) + + test('getLocalConfig (undefined)', async () => { + expect(lf.getLocalConfig().isDefault()).toEqual(true) + }) + + test('getLocalConfigWithSecrets (undefined config)', async () => { + const expected = new LogForwarding.LogForwardingConfig() + expect(lf.getLocalConfigWithSecrets()).toEqual(expected) + }) + + test('getServerConfig', async () => { + rtLib.logForwarding.get = jest.fn().mockReturnValue({ + destination: { + updated_at: '2021-08-27T14:40:06.000+00:00', + some_key: 'some_value' + } + }) + const expected = new LogForwarding.LogForwardingConfig('destination', { + updated_at: '2021-08-27T14:40:06.000+00:00', + some_key: 'some_value' + }) + expect(await lf.getServerConfig()).toEqual(expected) + }) + + test('getServerConfig (failed response)', async () => { + rtLib.logForwarding.get = jest.fn().mockRejectedValue(new Error('mocked error')) + await expect(lf.getServerConfig()).rejects.toThrow('mocked error') + }) + + test.each([ + [0, {}], + [2, { + destination1: {}, + destination2: {} + }] + ])('getServerConfig (incorrectly defined multiple destinations)', async (expectedDestinations, settings) => { + rtLib.logForwarding.get = jest.fn().mockReturnValue(settings) + await expect(() => lf.getServerConfig()) + .rejects + .toThrow(`Incorrect log forwarding configuration on server. Configuration has ${expectedDestinations} destinations. Exactly one must be defined.`) + }) + + test('getSupportedDestinations', async () => { + const destinations = { value: 'val', name: 'name' } + rtLib.logForwarding.getSupportedDestinations = jest.fn().mockReturnValue(destinations) + expect(lf.getSupportedDestinations()).toEqual({ value: 'val', name: 'name' }) + }) + + test('getSettingsConfig', async () => { + const config = [{ + name: 'name', + message: 'message', + type: 'type' + }] + rtLib.logForwarding.getDestinationSettings = jest.fn().mockReturnValue(config) + expect(lf.getSettingsConfig('destination')).toEqual(config) + }) + + test('updateLocalConfig', async () => { + const newConfig = new LogForwarding.LogForwardingConfig('destination', { + field: 'val', + secret_field: 'secret' + }) + + const expectedNonSecretConfig = { + project: { + workspace: { + log_forwarding: { + destination: { + field: 'val' + } + } + } + } + } + + const expectedSecretConfig = { + DESTINATION__SECRET_FIELD: 'secret' + } + const interactive = false + const merge = true + + await lf.updateLocalConfig(newConfig) + expect(writeAio).toHaveBeenCalledWith(expectedNonSecretConfig, '', { interactive, merge }) + expect(writeEnv).toHaveBeenCalledWith({}, '', { interactive, merge }, expectedSecretConfig) + }) +}) + +test.each([ + [0, {}], + [2, { + destination1: {}, + destination2: {} + }] +])('getLocalConfig (incorrectly defined destinations)', async (expectedDestinations, settings) => { + const aioConfig = { + project: { + workspace: { + log_forwarding: settings + } + }, + runtime: rtConfig + } + lf = await LogForwarding.init(aioConfig) + expect(() => lf.getLocalConfig()) + .toThrow(`Incorrect local log forwarding configuration. Configuration has ${expectedDestinations} destinations. Exactly one must be defined.`) +}) + +describe('with checksum file', () => { + beforeEach(() => { + fs.readFileSync.mockReset() + }) + + test('isLocalConfigChanged (new config - no checksum)', async () => { + fs.pathExistsSync.mockReturnValue(false) + expect(lf.isLocalConfigChanged()).toEqual(true) + expect(fs.pathExistsSync).toHaveBeenCalledWith(LF_CONFIGPATH) + expect(fs.readFileSync).toHaveBeenCalledTimes(0) + }) + + test('updateServerConfig', async () => { + const settings = { + new_field: 'new value', + new_secret_field: 'new secret' + } + const sanitizedSettings = { + new_field: 'new value sanitized', + new_secret_field: 'new secret sanitized' + } + + rtLib.logForwarding.setDestination = jest.fn().mockResolvedValue({ + new_destination: sanitizedSettings + }) + + const config = new LogForwarding.LogForwardingConfig('new_destination', settings) + + expect(await lf.updateServerConfig(config)).toEqual({ + new_destination: sanitizedSettings + }) + expect(rtLib.logForwarding.setDestination).toHaveBeenCalledWith('new_destination', settings) + expect(fs.ensureDirSync).toHaveBeenCalledWith('dist') + expect(fs.writeFile).toHaveBeenCalledWith( + LF_CONFIGPATH, + 'a431a2616cbca2a7f017d3829dceb25d0f90f4dc285e0fa74796fa223576ea96', + { flags: 'w' } + ) + }) +}) + +test('log forwarding configs are equal', () => { + const config1 = new LogForwarding.LogForwardingConfig('destination', { field: 'value', updated_at: 'some time ago' }) + const config2 = new LogForwarding.LogForwardingConfig('destination', { field: 'value' }) + expect(config1.isEqual(config2)).toEqual(true) + expect(config2.isEqual(config1)).toEqual(true) +}) + +test('absent and default log forwarding configs are equal', () => { + const config1 = new LogForwarding.LogForwardingConfig(undefined, { }) + const config2 = new LogForwarding.LogForwardingConfig('adobe_io_runtime', { }) + expect(config1.isEqual(config2)).toEqual(true) + expect(config2.isEqual(config1)).toEqual(true) +}) + +test.each([ + [ + 'different destinations with identical settings', + 'destination1', + 'destination2', + { field: 'value' }, + { field: 'value' } + ], + [ + 'different destinations and setting values', + 'destination1', + 'destination2', + { field: 'value' }, + { field: 'another value' } + ], + [ + 'different destinations and setting fields', + 'destination1', + 'destination2', + { field: 'value' }, + { field: 'value', another_field: 'another value' } + ], + [ + 'same destinations and different setting values', + 'destination', + 'destination', + { field: 'value' }, + { field: 'another value' } + ], + [ + 'same destinations and different setting fields', + 'destination', + 'destination', + { field: 'value' }, + { field: 'value', another_field: 'another value' } + ] +])('%s', (fixtureName, dst1, dst2, dstConfig1, dstConfig2) => { + const config1 = new LogForwarding.LogForwardingConfig(dst1, dstConfig1) + const config2 = new LogForwarding.LogForwardingConfig(dst2, dstConfig2) + expect(config1.isEqual(config2)).toEqual(false) + expect(config2.isEqual(config1)).toEqual(false) +})