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

fix: ACNA-3397 - web action does not have web in the url, resulting in an invalid link #197

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
292 changes: 121 additions & 171 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/readme_template.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
Copyright 2020 Adobe. All rights reserved.
Copyright 2024 Adobe. All rights reserved.
shazron marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
25 changes: 25 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.js',
'!src/types.jsdoc.js'
],
coverageThreshold: {
global: {
branches: 100,
lines: 100,
statements: 100
}
},
testPathIgnorePatterns: [
'<rootDir>/jest.setup.js'
],
reporters: [
'default',
'jest-junit'
],
testEnvironment: 'node',
setupFilesAfterEnv: [
'./test/jest.setup.js'
]
}
39 changes: 7 additions & 32 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@
"description": "Adobe I/O Runtime Lib",
"devDependencies": {
"@adobe/aio-lib-test-proxy": "^1.0.0",
"@adobe/eslint-config-aio-lib-config": "^2.0.1",
"@adobe/eslint-config-aio-lib-config": "^4.0.0",
"@types/jest": "^29.5.0",
"@types/node-fetch": "^2.5.4",
"babel-runtime": "^6.26.0",
"codecov": "^3.5.0",
"dotenv": "^16.3.1",
"eol": "^0.9.1",
"eslint": "^8.47.0",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-jest": "^27.2.3",
"eslint-plugin-jsdoc": "^42.0.0",
"eslint-plugin-n": "^15.7",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jsdoc": "^48.11.0",
"eslint-plugin-n": "15.7",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-promise": "^6.6.0",
"jest": "^29",
"jest-fetch-mock": "^3.0.2",
"jest-junit": "^16.0.0",
Expand All @@ -69,30 +69,5 @@
"typings": "jsdoc -t node_modules/tsd-jsdoc/dist -r src -d .",
"unit-tests": "jest --ci",
"version": "npm run generate-docs && git add README.md"
},
"jest": {
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*.js",
"!src/types.jsdoc.js"
],
"coverageThreshold": {
"global": {
"branches": 100,
"lines": 100,
"statements": 100
}
},
"testPathIgnorePatterns": [
"<rootDir>/jest.setup.js"
],
"reporters": [
"default",
"jest-junit"
],
"testEnvironment": "node",
"setupFilesAfterEnv": [
"./test/jest.setup.js"
]
}
}
33 changes: 21 additions & 12 deletions src/deploy-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,30 @@ const IOruntime = require('./RuntimeAPI')
const PACKAGE_ITEMS = ['actions', 'sequences']
const FILTERABLE_ITEMS = ['apis', 'triggers', 'rules', 'dependencies', ...PACKAGE_ITEMS]
const { createHash } = require('node:crypto')

/**
* @typedef {object} FilterEntities
* @property {Array} [actions] filter list of actions to deploy by provided array, e.g. ['name1', ..]
* @property {boolean} [byBuiltActions] if true, trim actions from the manifest based on the already built actions
* @property {Array} [sequences] filter list of sequences to deploy, e.g. ['name1', ..]
* @property {Array} [triggers] filter list of triggers to deploy, e.g. ['name1', ..]
* @property {Array} [rules] filter list of rules to deploy, e.g. ['name1', ..]
* @property {Array} [apis] filter list of apis to deploy, e.g. ['name1', ..]
* @property {Array} [dependencies] filter list of package dependencies to deploy, e.g. ['name1', ..]
*/

/**
* @typedef {object} DeployConfig
* @property {boolean} [isLocalDev] local dev flag // todo: remove
* @property {FilterEntities} [filterEntities] add filters to deploy only specified OpenWhisk entities
* @property {boolean} [useForce] force deploy of actions
*/

/**
* runs the command
* Runs the command
*
* @param {object} config app config
* @param {object} [deployConfig={}] deployment config
* @param {boolean} [deployConfig.isLocalDev] local dev flag // todo: remove
* @param {object} [deployConfig.filterEntities] add filters to deploy only specified OpenWhisk entities
* @param {Array} [deployConfig.filterEntities.actions] filter list of actions to deploy by provided array, e.g. ['name1', ..]
* @param {boolean} [deployConfig.filterEntities.byBuiltActions] if true, trim actions from the manifest based on the already built actions
* @param {Array} [deployConfig.filterEntities.sequences] filter list of sequences to deploy, e.g. ['name1', ..]
* @param {Array} [deployConfig.filterEntities.triggers] filter list of triggers to deploy, e.g. ['name1', ..]
* @param {Array} [deployConfig.filterEntities.rules] filter list of rules to deploy, e.g. ['name1', ..]
* @param {Array} [deployConfig.filterEntities.apis] filter list of apis to deploy, e.g. ['name1', ..]
* @param {Array} [deployConfig.filterEntities.dependencies] filter list of package dependencies to deploy, e.g. ['name1', ..]
* @param {boolean} [deployConfig.useForce] force deploy of actions
* @param {DeployConfig} [deployConfig] deployment config
* @param {object} [logFunc] custom logger function
* @returns {Promise<object>} deployedEntities
*/
Expand Down
149 changes: 82 additions & 67 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ const path = require('path')
const archiver = require('archiver')
// this is a static list that comes from here: https://developer.adobe.com/runtime/docs/guides/reference/runtimes/
const SupportedRuntimes = ['sequence', 'blackbox', 'nodejs:10', 'nodejs:12', 'nodejs:14', 'nodejs:16', 'nodejs:18', 'nodejs:20', 'nodejs:22']

const DEFAULT_PACKAGE_RESERVED_NAME = 'default'
const ANNOTATION_WEB_EXPORT = 'web-export'
const ANNOTATION_RAW_HTTP = 'raw-http'
const ANNOTATION_REQUIRE_ADOBE_AUTH = 'require-adobe-auth'
const ANNOTATION_REQUIRE_WHISK_AUTH = 'require-whisk-auth'
const VALUE_YES = 'yes'
const VALUE_RAW = 'raw'

/**
*
Expand Down Expand Up @@ -60,7 +67,6 @@ const DEFAULT_PACKAGE_RESERVED_NAME = 'default'
* @property {Array<object>} [limits] limits for the action
* @property {string} [web] indicate if an action should be exported as web, can take the
* value of: true | false | yes | no | raw
* @property {string} [web-export] same as web
* @property {boolean} [raw-http] indicate if an action should be exported as raw web action, this
* option is only valid if `web` or `web-export` is set to true
* @property {string} [docker] the docker container to run the action into
Expand Down Expand Up @@ -814,30 +820,23 @@ function returnDeploymentTriggerInputs (deploymentPackages) {
* @returns {object} the action annotation entities
*/
function returnAnnotations (action) {
const annotationParams = action && action.annotations ? cloneDeep(action.annotations) : {}
const annotationParams = cloneDeep(action.annotations ?? {})
// common annotations
if (action.annotations && action.annotations.conductor !== undefined) {
if (action.annotations?.conductor !== undefined) {
annotationParams.conductor = action.annotations.conductor
}

// web related annotations
if (action.web !== undefined) {
Object.assign(annotationParams, checkWebFlags(action.web))
} else if (action['web-export'] !== undefined) {
Object.assign(annotationParams, checkWebFlags(action['web-export']))
} else {
annotationParams['web-export'] = false
annotationParams['raw-http'] = false
}
const webAnnotations = processAnnotationsForWebAction(action)
Object.assign(annotationParams, webAnnotations)

if (action.annotations && annotationParams['web-export'] === true) {
if (action.annotations['require-whisk-auth'] !== undefined) {
annotationParams['require-whisk-auth'] = action.annotations['require-whisk-auth']
if (annotationParams[ANNOTATION_WEB_EXPORT] === true) {
if (action.annotations?.[ANNOTATION_REQUIRE_WHISK_AUTH] !== undefined) {
annotationParams[ANNOTATION_REQUIRE_WHISK_AUTH] = action.annotations[ANNOTATION_REQUIRE_WHISK_AUTH]
}
if (action.annotations['raw-http'] !== undefined) {
annotationParams['raw-http'] = action.annotations['raw-http']
if (action.annotations?.[ANNOTATION_RAW_HTTP] !== undefined) {
annotationParams[ANNOTATION_RAW_HTTP] = action.annotations[ANNOTATION_RAW_HTTP]
}
if (action.annotations.final !== undefined) {
if (action.annotations?.final !== undefined) {
annotationParams.final = action.annotations.final
}
}
Expand Down Expand Up @@ -905,7 +904,7 @@ function createApiRoutes (pkg, pkgName, apiName, allowedActions, allowedSequence
}

// ensure action or sequence has the web annotation
if (!actionDefinition.web && !actionDefinition['web-export']) {
if (!actionDefinition.web && !actionDefinition.annotations?.[ANNOTATION_WEB_EXPORT]) {
throw new Error('Action or sequence provided in api is not a web action')
}

Expand Down Expand Up @@ -954,27 +953,48 @@ function createSequenceObject (fullName, manifestSequence, packageName) {
}

/**
* @description Check the web flags
* @param {string|boolean} flag the flag to check
* @returns {object} object with the appropriate web flags for an action
* A field value can either be true, false, 'yes', 'no', or 'raw'
* For example the action `web` field, or action `web-export` annotation can have all these values.
* Other fields might only have boolean as valid.
*
* Falsy values are false, 'no'
* Truthy values are true, 'yes', and 'raw'
*
* @private
* @param {string | boolean} webFieldValue the web value
* @returns {boolean} true if truthy, or else false
*/
function isTruthyFieldValue (webFieldValue) {
return (
webFieldValue === true ||
webFieldValue === VALUE_YES ||
webFieldValue === VALUE_RAW
)
}

/**
* Process the annotations for the web action.
*
* The action's web property will determine whether the action annotation's
* `web-export` and/or `raw-http` properties are to be set.
*
* @private
* @param {object} action the action to process
* @returns {object} the processed action annotations
*/
function checkWebFlags (flag) {
const tempObj = {}
switch (flag) {
case true:
case 'yes' :
tempObj['web-export'] = true
break
case 'raw' :
tempObj['web-export'] = true
tempObj['raw-http'] = true
break
case false:
case 'no':
tempObj['web-export'] = false
tempObj['raw-http'] = false
function processAnnotationsForWebAction (action) {
const isRawValue = (value) => value === 'raw'

const isWeb = isTruthyFieldValue(action.web)
const isWebExport = isTruthyFieldValue(action.annotations?.[ANNOTATION_WEB_EXPORT])
const isWebRawValue = (isRawValue(action.web) || isRawValue(action.annotations?.[ANNOTATION_WEB_EXPORT]))
const isRaw = isTruthyFieldValue(action.annotations?.[ANNOTATION_RAW_HTTP]) || isWebRawValue
const isWebOrWebExport = isWeb || isWebExport

return {
[ANNOTATION_WEB_EXPORT]: isWebOrWebExport,
[ANNOTATION_RAW_HTTP]: isRaw && isWebOrWebExport
}
return tempObj
}

/**
Expand Down Expand Up @@ -1070,7 +1090,6 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
[PROD_ENV]: '/adobeio/shared-validators-v1/headless-v2',
[STAGE_ENV]: '/adobeio-stage/shared-validators-v1/headless-v2'
}
const ADOBE_AUTH_ANNOTATION = 'require-adobe-auth'
const ADOBE_AUTH_ACTION = ADOBE_AUTH_ACTIONS[env]
const REWRITE_ACTION_PREFIX = '__secured_'

Expand All @@ -1083,51 +1102,48 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
if (newPackages[key].actions) {
Object.keys(newPackages[key].actions).forEach((actionName) => {
const thisAction = newPackages[key].actions[actionName]

const isWebExport = checkWebFlags(thisAction['web-export'])['web-export']
const isWeb = checkWebFlags(thisAction.web)['web-export']
const isRaw = checkWebFlags(thisAction.web)['raw-http'] || checkWebFlags(thisAction['web-export'])['raw-http']
Object.assign(thisAction.annotations, processAnnotationsForWebAction(thisAction))
const isWeb = thisAction.annotations?.[ANNOTATION_WEB_EXPORT]
const isRaw = thisAction.annotations?.[ANNOTATION_RAW_HTTP]

// check if the annotation is defined
if (thisAction.annotations?.[ADOBE_AUTH_ANNOTATION]) {
aioLogger.debug(`found annotation '${ADOBE_AUTH_ANNOTATION}' in action '${key}/${actionName}', cli env = ${env}`)
if (thisAction.annotations?.[ANNOTATION_REQUIRE_ADOBE_AUTH]) {
aioLogger.debug(`found annotation '${ANNOTATION_REQUIRE_ADOBE_AUTH}' in action '${key}/${actionName}', cli env = ${env}`)

// check if the action is a web action
if (!(isWeb || isWebExport)) {
aioLogger.warn(`The action '${key}/${actionName}' is not a web action, the annotation '${ADOBE_AUTH_ANNOTATION}' will be ignored.`)
if (!isWeb) {
aioLogger.warn(`The action '${key}/${actionName}' is not a web action, the annotation '${ANNOTATION_REQUIRE_ADOBE_AUTH}' will be ignored.`)
return
}

// 1. rename the action
const renamedAction = REWRITE_ACTION_PREFIX + actionName
const renamedActionName = REWRITE_ACTION_PREFIX + actionName
/* istanbul ignore if */
if (newPackages[key].actions[renamedAction] !== undefined) {
if (newPackages[key].actions[renamedActionName] !== undefined) {
// unlikely
throw new Error(`Failed to rename the action '${key}/${actionName}' to '${key}/${renamedAction}': an action with the same name exists already.`)
throw new Error(`Failed to rename the action '${key}/${actionName}' to '${key}/${renamedActionName}': an action with the same name exists already.`)
}

// set the action to the new key
newPackages[key].actions[renamedAction] = thisAction
newPackages[key].actions[renamedActionName] = thisAction
// delete the old key
delete newPackages[key].actions[actionName]

// make sure any content in the deployment package is linked to the new action name
if (newDeploymentPackages[key] && newDeploymentPackages[key].actions && newDeploymentPackages[key].actions[actionName]) {
newDeploymentPackages[key].actions[renamedAction] = newDeploymentPackages[key].actions[actionName]
newDeploymentPackages[key].actions[renamedActionName] = newDeploymentPackages[key].actions[actionName]
delete newDeploymentPackages[key].actions[actionName]
}

// 2. delete the adobe-auth annotation and secure the renamed action
// the renamed action is made secure by removing its web property
if (isWeb) {
newPackages[key].actions[renamedAction].web = false
}
if (isWebExport) {
newPackages[key].actions[renamedAction]['web-export'] = false
}
delete newPackages[key].actions[renamedAction].annotations[ADOBE_AUTH_ANNOTATION]
const renamedAction = newPackages[key].actions[renamedActionName]

delete renamedAction.web
delete renamedAction.annotations[ANNOTATION_WEB_EXPORT]
delete renamedAction.annotations[ANNOTATION_REQUIRE_ADOBE_AUTH]

aioLogger.debug(`renamed action '${key}/${actionName}' to '${key}/${renamedAction}'`)
aioLogger.debug(`renamed action '${key}/${actionName}' to '${key}/${renamedActionName}'`)

// 3. create the sequence
if (newPackages[key].sequences === undefined) {
Expand All @@ -1140,11 +1156,11 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
}
// set the sequence content
newPackages[key].sequences[actionName] = {
actions: `${ADOBE_AUTH_ACTION},${key}/${renamedAction}`,
web: (isRaw && 'raw') || 'yes'
actions: `${ADOBE_AUTH_ACTION},${key}/${renamedActionName}`,
web: (isRaw && VALUE_RAW) || VALUE_YES
}

aioLogger.debug(`defined new sequence '${key}/${actionName}': '${ADOBE_AUTH_ACTION},${key}/${renamedAction}'`)
aioLogger.debug(`defined new sequence '${key}/${actionName}': '${ADOBE_AUTH_ACTION},${key}/${renamedActionName}'`)
}
})
}
Expand All @@ -1163,8 +1179,8 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
* @param {DeploymentPackages} deploymentPackages the deployment packages
* @param {object} deploymentTriggers the deployment triggers
* @param {object} params the package params
* @param {boolean} [namesOnly=false] if false, set the namespaces as well
* @param {object} [owOptions={}] additional OpenWhisk options
* @param {boolean} [namesOnly] if false, set the namespaces as well
* @param {object} [owOptions] additional OpenWhisk options
* @returns {OpenWhiskEntities} deployment entities
*/
function processPackage (packages,
Expand Down Expand Up @@ -1835,7 +1851,7 @@ function getActionUrls (appConfig, /* istanbul ignore next */ isRemoteDev = fals

/** @private */
function getActionUrl (pkgAndActionName, action) {
const webArg = action?.annotations?.['web-export'] || action?.web
const webArg = action?.annotations?.[ANNOTATION_WEB_EXPORT] || action?.web
const webUri = (webArg && webArg !== 'no' && webArg !== 'false') ? 'web' : ''

const actionIsBehindCdn =
Expand Down Expand Up @@ -2101,7 +2117,6 @@ module.exports = {
returnDeploymentTriggerInputs, /* internal */
getDeploymentPath, /* internal */
createActionObject, /* internal */
checkWebFlags, /* internal */
createSequenceObject, /* internal */
createApiRoutes, /* internal */
returnAnnotations, /* internal */
Expand Down
Loading
Loading