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

ACNA-3231 | E2e tests failure #815

Closed
wants to merge 15 commits into from
37 changes: 26 additions & 11 deletions src/commands/app/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const BaseCommand = require('../../BaseCommand')
const BuildCommand = require('./build')
const webLib = require('@adobe/aio-lib-web')
const { Flags } = require('@oclif/core')
const { createWebExportFilter, runInProcess, buildExtensionPointPayloadWoMetadata, buildExcShellViewExtensionMetadata, getCliInfo } = require('../../lib/app-helper')
const { createWebExportFilter, runInProcess, buildExtensionPointPayloadWoMetadata, buildExcShellViewExtensionMetadata, getCliInfo, checkifAccessTokenExists } = require('../../lib/app-helper')
const rtLib = require('@adobe/aio-lib-runtime')
const LogForwarding = require('../../lib/log-forwarding')
const { sendAuditLogs, getAuditLogEvent, getFilesCountWithExtension } = require('../../lib/audit-logger')
Expand All @@ -36,6 +36,7 @@ class Deploy extends BuildCommand {
// Deploy only a specific action, the flags can be specified multiple times, this will set --no-publish
flags.publish = flags.publish && !flags.action

const doesTokenExists = await checkifAccessTokenExists()
const deployConfigs = await this.getAppExtConfigs(flags)
const keys = Object.keys(deployConfigs)
const values = Object.values(deployConfigs)
Expand All @@ -53,7 +54,7 @@ class Deploy extends BuildCommand {

try {
const aioConfig = (await this.getFullConfig()).aio
const cliDetails = await getCliInfo()
const loginCredentials = doesTokenExists ? await getCliInfo() : null

// 1. update log forwarding configuration
// note: it is possible that .aio file does not exist, which means there is no local lg config
Expand All @@ -65,7 +66,7 @@ class Deploy extends BuildCommand {
const lfConfig = lf.getLocalConfigWithSecrets()
if (lfConfig.isDefined()) {
await lf.updateServerConfig(lfConfig)
spinner.succeed(chalk.green(`Log forwarding is set to '${lfConfig.getDestination()}'`))
spinner.succeed(chalk.green(`\nLog forwarding is set to '${lfConfig.getDestination()}'`))
} else {
if (flags.verbose) {
spinner.info(chalk.dim('Log forwarding is not updated: no configuration is provided'))
Expand Down Expand Up @@ -95,12 +96,21 @@ class Deploy extends BuildCommand {
}

// 3. send deploy log event
const logEvent = getAuditLogEvent(flags, aioConfig.project, 'AB_APP_DEPLOY')
if (logEvent) {
await sendAuditLogs(cliDetails.accessToken, logEvent, cliDetails.env)
} else {
this.log(chalk.red(chalk.bold('Warning: No valid config data found to send audit log event for deployment.')))
}

// TODO: We will need this code running when Once Runtime events start showing up,
// we'll decide on the fate of the 'Starting deployment/undeplpyment' messages.
// JIRA: https://jira.corp.adobe.com/browse/ACNA-3240

// const logEvent = getAuditLogEvent(flags, aioConfig.project, 'AB_APP_DEPLOY')
// if (logEvent && loginCredentials) {
// try {
// await sendAuditLogs(loginCredentials.accessToken, logEvent, loginCredentials.env)
// } catch (error) {
// this.log(chalk.red(chalk.bold('Error: Audit Log Service Error: Failed to send audit log event for deployment.')))
// }
// } else {
// this.log(chalk.red(chalk.bold('Warning: No valid config data found to send audit log event for deployment.')))
// }

// 4. deploy actions and web assets for each extension
// Possible improvements:
Expand All @@ -113,9 +123,14 @@ class Deploy extends BuildCommand {
if (v.app.hasFrontend && flags['web-assets']) {
const opItems = getFilesCountWithExtension(v.web.distProd)
const assetDeployedLogEvent = getAuditLogEvent(flags, aioConfig.project, 'AB_APP_ASSETS_DEPLOYED')
if (assetDeployedLogEvent) {
if (assetDeployedLogEvent && loginCredentials) {
assetDeployedLogEvent.data.opItems = opItems
await sendAuditLogs(cliDetails.accessToken, assetDeployedLogEvent, cliDetails.env)
try {
// only send logs in case of web-assets deployment
await sendAuditLogs(loginCredentials.accessToken, assetDeployedLogEvent, loginCredentials.env)
} catch (error) {
this.log(chalk.red(chalk.bold('Error: Audit Log Service Error: Failed to send audit log event for deployment.')))
amulyakashyap09 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Expand Down
38 changes: 27 additions & 11 deletions src/commands/app/undeploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const { Flags } = require('@oclif/core')

const BaseCommand = require('../../BaseCommand')
const webLib = require('@adobe/aio-lib-web')
const { runInProcess, buildExtensionPointPayloadWoMetadata, getCliInfo } = require('../../lib/app-helper')
const { runInProcess, buildExtensionPointPayloadWoMetadata, getCliInfo, checkifAccessTokenExists } = require('../../lib/app-helper')
const rtLib = require('@adobe/aio-lib-runtime')
const { sendAuditLogs, getAuditLogEvent } = require('../../lib/audit-logger')

Expand All @@ -26,6 +26,8 @@ class Undeploy extends BaseCommand {
// cli input
const { flags } = await this.parse(Undeploy)

const doesTokenExists = await checkifAccessTokenExists()

const undeployConfigs = await this.getAppExtConfigs(flags)
let libConsoleCLI
if (flags.unpublish) {
Expand All @@ -46,23 +48,37 @@ class Undeploy extends BaseCommand {
const spinner = ora()
try {
const aioConfig = (await this.getFullConfig()).aio
const cliDetails = await getCliInfo()
const logEvent = getAuditLogEvent(flags, aioConfig.project, 'AB_APP_UNDEPLOY')
const loginCredentials = doesTokenExists ? await getCliInfo() : null

// 1.1. send audit log event for successful undeploy
if (logEvent) {
await sendAuditLogs(cliDetails.accessToken, logEvent, cliDetails.env)
} else {
this.log(chalk.red(chalk.bold('Warning: No valid config data found to send audit log event for deployment.')))
}
// TODO: We will need this code running when Once Runtime events start showing up,
// we'll decide on the fate of the 'Starting deployment/undeplpyment' messages.
// JIRA: https://jira.corp.adobe.com/browse/ACNA-3240

// const logEvent = getAuditLogEvent(flags, aioConfig.project, 'AB_APP_UNDEPLOY')

// // 1.1. send audit log event for successful undeploy
// if (logEvent && loginCredentials) {
// try {
// await sendAuditLogs(loginCredentials.accessToken, logEvent, loginCredentials.env)
// } catch (error) {
// this.warn('Warning: Audit Log Service Error: Failed to send audit log event for un-deployment.')
// }
// } else {
// this.warn('Warning: No valid config data found to send audit log event for un-deployment.')
// }

for (let i = 0; i < keys.length; ++i) {
const k = keys[i]
const v = values[i]
await this.undeployOneExt(k, v, flags, spinner)
const assetUndeployLogEvent = getAuditLogEvent(flags, aioConfig.project, 'AB_APP_ASSETS_UNDEPLOYED')
if (assetUndeployLogEvent) {
await sendAuditLogs(cliDetails.accessToken, assetUndeployLogEvent, cliDetails.env)
// send logs for case of web-assets undeployment
if (assetUndeployLogEvent && loginCredentials) {
try {
await sendAuditLogs(loginCredentials.accessToken, assetUndeployLogEvent, loginCredentials.env)
} catch (error) {
this.warn('Warning: Audit Log Service Error: Failed to send audit log event for un-deployment.')
}
}
}

Expand Down
23 changes: 21 additions & 2 deletions src/lib/app-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ async function runScript (command, dir, cmdArgs = []) {
aioLogger.debug(`Killing ${command} event hook long-running process (pid: ${pid})`)
process.kill(pid, 'SIGTERM')
} catch (_) {
// do nothing if pid not found
// do nothing if pid not found
}
})
}
Expand Down Expand Up @@ -567,6 +567,24 @@ function getObjectValue (obj, key) {
return keys.filter(o => o.trim()).reduce((o, i) => o && getObjectProp(o, i), obj)
}

/**
* Function to run `aio config get ims.contexts.cli.access_token.token` command and retrieve the token
* This function is used to check if the access token exists in the CLI context or not
* @returns {Promise<boolean>} Resolves to the access token if it exists, else false
*/
const checkifAccessTokenExists = async () => {
try {
const token = aioConfig.get('ims.contexts.cli.access_token.token')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is wrong for a lot of reasons.

  • login context can be different things in different cases
  • we sidestep using IMS lib, but seem to know everything about it ...
  • what if the token is expired? we assume because it exists it is still good.
  • why even call IMS if we already have the token?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR is updated with solution for above concern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@purplecabbage I have covered and tested (locally on my machine) for all the scenarios:

  1. deploy command for normal web app (non-standalone app)
  2. undeploy command for normal web app (non-standalone app)
  3. deploy command for standalone app (where no-token is required)
  4. undeploy command for standalone app (where no-token is required)

What if Token is expired Scenario : https://adobeio.slack.com/archives/C04HFLVUDLM/p1728303835889539?thread_ts=1728302643.048849&cid=C04HFLVUDLM

if (token) {
return true
}
return false
} catch (error) {
console.error(`Error retrieving token: ${error.message}`)
return false
}
}

module.exports = {
getObjectValue,
getObjectProp,
Expand Down Expand Up @@ -596,5 +614,6 @@ module.exports = {
buildExtensionPointPayloadWoMetadata,
buildExcShellViewExtensionMetadata,
atLeastOne,
deleteUserConfig
deleteUserConfig,
checkifAccessTokenExists
}
20 changes: 4 additions & 16 deletions src/lib/audit-logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ const fetch = require('node-fetch')
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const { getCliEnv, PROD_ENV } = require('@adobe/aio-lib-env')
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:lib-audit-logger', { provider: 'debug' })

const OPERATIONS = {
AB_APP_DEPLOY: 'ab_app_deploy',
AB_APP_UNDEPLOY: 'ab_app_undeploy',
AB_APP_TEST: 'ab_app_test', // todo : remove after testing
AB_APP_TEST: 'ab_app_test',
AB_APP_ASSETS_DEPLOYED: 'ab_app_assets_deployed',
AB_APP_ASSETS_UNDEPLOYED: 'ab_app_assets_undeployed'
}

const AUDIT_SERVICE_ENPOINTS = {
const AUDIT_SERVICE_ENDPOINTS = {
stage: 'https://adp-auditlog-service-stage.adobeioruntime.net/api/v1/web/audit-log-api/event-post',
prod: 'https://adp-auditlog-service-prod.adobeioruntime.net/api/v1/web/audit-log-api/event-post'
amulyakashyap09 marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -35,12 +33,7 @@ const AUDIT_SERVICE_ENPOINTS = {
* @param {string} env valid env stage|prod
*/
async function sendAuditLogs (accessToken, logEvent, env = 'prod') {
// TODO: this is blocked by the audit service only being available in stage
// remove this check once the service is available in prod
if (env !== 'stage') {
return
}
const url = AUDIT_SERVICE_ENPOINTS[env]
const url = AUDIT_SERVICE_ENDPOINTS[env] ?? AUDIT_SERVICE_ENDPOINTS.prod
const payload = {
event: logEvent
}
Expand All @@ -67,11 +60,6 @@ async function sendAuditLogs (accessToken, logEvent, env = 'prod') {
* @returns {object} logEvent
*/
function getAuditLogEvent (flags, project, event) {
if (getCliEnv() === PROD_ENV) {
aioLogger.debug('Audit logging is currently disabled in production environment')
return null
}

let logEvent, logStrMsg
if (project && project.org && project.workspace) {
if (event === 'AB_APP_DEPLOY') {
Expand Down Expand Up @@ -151,6 +139,6 @@ function getFilesCountWithExtension (directory) {
module.exports = {
sendAuditLogs,
getAuditLogEvent,
AUDIT_SERVICE_ENPOINTS,
AUDIT_SERVICE_ENDPOINTS,
getFilesCountWithExtension
}
93 changes: 90 additions & 3 deletions test/commands/app/deploy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ beforeEach(() => {
}
})
LogForwarding.init.mockResolvedValue(mockLogForwarding)
helpers.checkifAccessTokenExists.mockResolvedValue(true)
})

test('exports', async () => {
Expand All @@ -217,7 +218,7 @@ test('flags', async () => {
expect(typeof TheCommand.flags.action).toBe('object')
expect(TheCommand.flags.action.char).toBe('a')
expect(typeof TheCommand.flags.action.description).toBe('string')
expect(JSON.stringify(TheCommand.flags.action.exclusive)).toStrictEqual(JSON.stringify(['extension', { name: 'publish', when: () => {} }]))
expect(JSON.stringify(TheCommand.flags.action.exclusive)).toStrictEqual(JSON.stringify(['extension', { name: 'publish', when: () => { } }]))

expect(typeof TheCommand.flags['web-assets']).toBe('object')
expect(typeof TheCommand.flags['web-assets'].description).toBe('string')
Expand Down Expand Up @@ -1324,11 +1325,11 @@ describe('run', () => {
expect(command.error).toHaveBeenCalledTimes(0)
expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1)
expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1)
expect(auditLogger.sendAuditLogs.mock.calls.length).toBeLessThanOrEqual(2)
expect(auditLogger.sendAuditLogs).toHaveBeenCalledTimes(1)
expect(auditLogger.sendAuditLogs).toHaveBeenCalledWith(mockToken, expect.objectContaining({ orgId: mockOrg, projectId: mockProject, workspaceId: mockWorkspaceId, workspaceName: mockWorkspaceName }), mockEnv)
})

test('Do not send audit logs for successful app deploy', async () => {
test('Do not send audit logs for successful app deploy, if no logevent is present', async () => {
const mockToken = 'mocktoken'
const mockEnv = 'stage'
const mockOrg = 'mockorg'
Expand Down Expand Up @@ -1362,6 +1363,45 @@ describe('run', () => {
expect(command.error).toHaveBeenCalledTimes(0)
expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1)
expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1)
expect(auditLogger.sendAuditLogs).toHaveBeenCalledTimes(0)
})

test('Do not send audit logs for successful app deploy, if no-login case', async () => {
const mockToken = 'mocktoken'
const mockEnv = 'stage'
const mockOrg = 'mockorg'
const mockProject = 'mockproject'
const mockWorkspaceId = 'mockworkspaceid'
const mockWorkspaceName = 'mockworkspacename'
helpers.getCliInfo.mockResolvedValueOnce({
accessToken: mockToken,
env: mockEnv
})
helpers.checkifAccessTokenExists.mockResolvedValueOnce(false)
command.getFullConfig = jest.fn().mockReturnValue({
aio: {
project: {
id: mockProject,
org: {
id: mockOrg
},
workspace: {
id: mockWorkspaceId,
name: mockWorkspaceName
}
}
}
})

auditLogger.getAuditLogEvent.mockImplementation((flags, project, event) => null)

command.getAppExtConfigs.mockResolvedValueOnce(createAppConfig(command.appConfig))

await command.run()
expect(command.error).toHaveBeenCalledTimes(0)
expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1)
expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1)
expect(auditLogger.sendAuditLogs).toHaveBeenCalledTimes(0)
})

test('Send audit logs for successful app deploy + web assets', async () => {
Expand Down Expand Up @@ -1403,4 +1443,51 @@ describe('run', () => {
expect(auditLogger.getFilesCountWithExtension).toHaveBeenCalledTimes(2)
expect(auditLogger.sendAuditLogs).toHaveBeenCalledWith(mockToken, expect.objectContaining({ orgId: mockOrg, projectId: mockProject, workspaceId: mockWorkspaceId, workspaceName: mockWorkspaceName }), mockEnv)
})

test('Should deploy successfully even if Audit log service is unavailable', async () => {
const mockToken = 'mocktoken'
const mockEnv = 'stage'
const mockOrg = 'mockorg'
const mockProject = 'mockproject'
const mockWorkspaceId = 'mockworkspaceid'
const mockWorkspaceName = 'mockworkspacename'
helpers.getCliInfo.mockResolvedValueOnce({
accessToken: mockToken,
env: mockEnv
})
command.getFullConfig = jest.fn().mockReturnValue({
aio: {
project: {
id: mockProject,
org: {
id: mockOrg
},
workspace: {
id: mockWorkspaceId,
name: mockWorkspaceName
}
}
}
})

auditLogger.sendAuditLogs.mockRejectedValue({
message: 'Internal Server Error',
status: 500
})

command.getAppExtConfigs.mockResolvedValueOnce(createAppConfig(command.appConfig))

await command.run()
expect(command.log).toHaveBeenCalledWith(
expect.stringContaining('skipping publish phase...')
)

expect(command.log).toHaveBeenCalledWith(
expect.stringContaining('Successful deployment 🏄')
)
expect(auditLogger.sendAuditLogs).toHaveBeenCalledTimes(1)
expect(command.error).toHaveBeenCalledTimes(0)
expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1)
expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1)
})
})
Loading