Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add proxy support #69

Merged
merged 18 commits into from
Oct 6, 2021
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
package-lock.json
coverage
junit.xml
.env
27 changes: 26 additions & 1 deletion e2e/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,39 @@ const sdk = require('../src/index')
const path = require('path')
const deepClone = require('lodash.clonedeep')
const fs = jest.requireActual('fs-extra')
const { createHttpsProxy } = require('@adobe/aio-lib-test-proxy')

jest.unmock('openwhisk')
jest.unmock('archiver')
jest.setTimeout(30000)

// load .env values in the e2e folder, if any
require('dotenv').config({ path: path.join(__dirname, '.env') })

let proxyServer
let sdkClient = {}
let config = {}
const apiKey = process.env['RuntimeAPI_API_KEY']
const apihost = process.env['RuntimeAPI_APIHOST'] || 'https://adobeioruntime.net'
const namespace = process.env['RuntimeAPI_NAMESPACE']
// console.log(apiKey)
const E2E_USE_PROXY = process.env.E2E_USE_PROXY
const HTTPS_PROXY = process.env.HTTPS_PROXY

beforeAll(async () => {
if (E2E_USE_PROXY) {
proxyServer = await createHttpsProxy()
console.log(`Using test proxy at ${proxyServer.url}`)
}

sdkClient = await sdk.init({ api_key: apiKey, apihost })
})

afterAll(() => {
if (proxyServer) {
proxyServer.stop()
}
})

beforeEach(() => {
config = deepClone(global.sampleAppConfig)
config.ow.namespace = namespace
Expand All @@ -41,6 +56,16 @@ beforeEach(() => {
config.manifest.src = path.resolve(config.root + '/' + 'manifest.yml')
})

// eslint-disable-next-line jest/expect-expect
test('HTTPS_PROXY must be set if E2E_USE_PROXY is set', () => {
// jest wraps process.env, so libraries will not pick up an env change via code change, so it has to be set on the shell level
if (E2E_USE_PROXY) {
if (!HTTPS_PROXY) {
throw new Error(`If you set E2E_USE_PROXY, you must set the HTTPS_PROXY environment variable. Please set it to HTTPS_PROXY=${proxyServer.url}.`)
}
}
})

describe('build-actions', () => {
test('full config', async () => {
expect(await sdk.buildActions(config)).toEqual(expect.arrayContaining([
Expand Down
File renamed without changes.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@
"dependencies": {
"@adobe/aio-lib-core-errors": "^3.0.0",
"@adobe/aio-lib-core-logging": "^1.1.2",
"@adobe/aio-lib-core-networking": "^2.0.0",
"@adobe/aio-lib-env": "^1.0.0",
"archiver": "^5.0.0",
"cross-fetch": "^3.0.4",
"execa": "^4.0.3",
"fs-extra": "^9.0.1",
"globby": "^11.0.1",
"js-yaml": "^3.14.0",
"lodash.clonedeep": "^4.5.0",
"openwhisk": "^3.21.2",
"openwhisk-fqn": "0.0.2",
"proxy-from-env": "^1.1.0",
"semver": "^7.3.2",
"sha1": "^1.1.1",
"webpack": "^5.26.3"
},
"deprecated": false,
"description": "Adobe I/O Runtime Lib",
"devDependencies": {
"@adobe/aio-lib-test-proxy": "^1.0.0",
"@adobe/eslint-config-aio-lib-config": "^1.3.0",
"@types/jest": "^26.0.4",
"@types/node-fetch": "^2.5.4",
Expand Down Expand Up @@ -57,7 +59,7 @@
"node": "^10 || ^12 || ^14"
},
"scripts": {
"e2e": "jest --config e2e/jest.config.js",
"e2e": "jest --config e2e/jest.config.js --runInBand",
"generate-docs": "npm run typings && npm run jsdoc",
"jsdoc": "jsdoc2md -t ./docs/readme_template.md src/**/*.js > README.md",
"lint": "eslint src test e2e",
Expand Down
4 changes: 3 additions & 1 deletion src/LogForwarding.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

const fetch = require('cross-fetch')
const { createFetch } = require('@adobe/aio-lib-core-networking')

/**
* Log Forwarding management API
Expand Down Expand Up @@ -97,6 +97,8 @@ class LogForwarding {
if (this.namespace === '_') {
throw new Error("Namespace '_' is not supported by log forwarding management API")
}

const fetch = createFetch()
return fetch(
this.apiHost + '/runtime/namespaces/' + this.namespace + '/logForwarding',
{
Expand Down
22 changes: 18 additions & 4 deletions src/RuntimeAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ governing permissions and limitations under the License.
const ow = require('openwhisk')
const { codes } = require('./SDKErrors')
const Triggers = require('./triggers')
const { getProxyForUrl } = require('proxy-from-env')
const deepCopy = require('lodash.clonedeep')
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-runtime:RuntimeAPI', { provider: 'debug', level: process.env.LOG_LEVEL })
const LogForwarding = require('./LogForwarding')

/**
Expand Down Expand Up @@ -50,20 +53,31 @@ class RuntimeAPI {
* @returns {Promise<OpenwhiskClient>} a RuntimeAPI object
*/
async init (options) {
aioLogger.debug(`init options: ${JSON.stringify(options, null, 2)}`)
const clonedOptions = deepCopy(options)

const initErrors = []
if (!options || !options.api_key) {
if (!clonedOptions || !clonedOptions.api_key) {
initErrors.push('api_key')
}
if (!options || !options.apihost) {
if (!clonedOptions || !clonedOptions.apihost) {
initErrors.push('apihost')
}

if (initErrors.length) {
const sdkDetails = { options }
const sdkDetails = { clonedOptions }
throw new codes.ERROR_SDK_INITIALIZATION({ sdkDetails, messageValues: `${initErrors.join(', ')}` })
}

this.ow = ow(options)
const proxyUrl = getProxyForUrl(clonedOptions.apihost)
if (proxyUrl) {
aioLogger.debug(`using proxy url: ${proxyUrl}`)
clonedOptions.proxy = proxyUrl
} else {
aioLogger.debug('proxy settings not found')
}

this.ow = ow(clonedOptions)
const self = this

return {
Expand Down
22 changes: 11 additions & 11 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ governing permissions and limitations under the License.
const fs = require('fs-extra')
const sha1 = require('sha1')
const cloneDeep = require('lodash.clonedeep')
const logger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-runtime:index', { level: process.env.LOG_LEVEL })
const debugLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-runtime:utils', { provider: 'debug' })
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-runtime:utils', { provider: 'debug', level: process.env.LOG_LEVEL })
const yaml = require('js-yaml')
const fetch = require('cross-fetch')
const { createFetch } = require('@adobe/aio-lib-core-networking')
const fetch = createFetch()
const globby = require('globby')
const path = require('path')
const archiver = require('archiver')
Expand Down Expand Up @@ -417,7 +417,7 @@ function getActionEntryFile (pkgJsonPath) {
return pkgJsonContent.main
}
} catch (err) {
debugLogger.debug(`File not found or does not define 'main' : ${pkgJsonPath}`)
aioLogger.debug(`File not found or does not define 'main' : ${pkgJsonPath}`)
}
return 'index.js'
}
Expand All @@ -431,7 +431,7 @@ function getActionEntryFile (pkgJsonPath) {
* @returns {Promise} returns with a blank promise when done
*/
function zip (filePath, out, pathInZip = false) {
debugLogger.debug(`Creating zip of file/folder ${filePath}`)
aioLogger.debug(`Creating zip of file/folder ${filePath}`)
const stream = fs.createWriteStream(out)
const archive = archiver('zip', { zlib: { level: 9 } })

Expand Down Expand Up @@ -496,7 +496,7 @@ function safeParse (val) {
try {
resultVal = JSON.parse(val)
} catch (ex) {
debugLogger.debug(`JSON parse threw exception for value ${val}`)
aioLogger.debug(`JSON parse threw exception for value ${val}`)
}
}
}
Expand Down Expand Up @@ -1060,7 +1060,7 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {

// check if the annotation is defined AND the action is a web action
if ((isWeb || isWebExport) && thisAction.annotations && thisAction.annotations[ADOBE_AUTH_ANNOTATION]) {
debugLogger.debug(`found annotation '${ADOBE_AUTH_ANNOTATION}' in action '${key}/${actionName}', cli env = ${env}`)
aioLogger.debug(`found annotation '${ADOBE_AUTH_ANNOTATION}' in action '${key}/${actionName}', cli env = ${env}`)

// 1. rename the action
const renamedAction = REWRITE_ACTION_PREFIX + actionName
Expand Down Expand Up @@ -1091,7 +1091,7 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
}
delete newPackages[key].actions[renamedAction].annotations[ADOBE_AUTH_ANNOTATION]

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

// 3. create the sequence
if (newPackages[key].sequences === undefined) {
Expand All @@ -1108,7 +1108,7 @@ function rewriteActionsWithAdobeAuthAnnotation (packages, deploymentPackages) {
web: (isRaw && 'raw') || 'yes'
}

debugLogger.debug(`defined new sequence '${key}/${actionName}': '${ADOBE_AUTH_ACTION},${key}/${renamedAction}'`)
aioLogger.debug(`defined new sequence '${key}/${actionName}': '${ADOBE_AUTH_ACTION},${key}/${renamedAction}'`)
}
})
}
Expand Down Expand Up @@ -1330,7 +1330,7 @@ function setPaths (flags = {}) {
} else {
manifestPath = flags.manifest
}
logger.debug(`Using manifest file: ${manifestPath}`)
aioLogger.debug(`Using manifest file: ${manifestPath}`)

let deploymentPath
let deploymentPackages = {}
Expand Down Expand Up @@ -1428,7 +1428,7 @@ async function setupAdobeAuth (actions, owOptions, imsOrgId) {
if (!res.ok) {
throw new Error(`failed setting ims_org_id=${imsOrgId} into state lib, received status=${res.status}, please make sure your runtime credentials are correct`)
}
logger.debug(`set IMS org id into cloud state, response: ${JSON.stringify(await res.json())}`)
aioLogger.debug(`set IMS org id into cloud state, response: ${JSON.stringify(await res.json())}`)
}
}

Expand Down
22 changes: 12 additions & 10 deletions test/LogForwarding.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const fetch = require('cross-fetch')
const LogForwarding = require('../src/LogForwarding')
const { createFetch } = require('@adobe/aio-lib-core-networking')
const mockFetch = jest.fn()

jest.mock('cross-fetch')
jest.mock('@adobe/aio-lib-core-networking')

const apiUrl = 'host/runtime/namespaces/some_namespace/logForwarding'

Expand All @@ -24,19 +25,20 @@ let logForwarding

beforeEach(async () => {
logForwarding = new LogForwarding('some_namespace', 'host', 'key')
fetch.mockReset()
createFetch.mockReturnValue(mockFetch)
mockFetch.mockReset()
})

test('get', async () => {
return new Promise(resolve => {
fetch.mockReturnValue(new Promise(resolve => {
mockFetch.mockReturnValue(new Promise(resolve => {
resolve({
json: jest.fn().mockResolvedValue('result')
})
}))
return logForwarding.get()
.then((res) => {
expect(fetch).toBeCalledTimes(1)
expect(mockFetch).toBeCalledTimes(1)
expect(res).toBe('result')
assertRequest('get')
resolve()
Expand All @@ -45,20 +47,20 @@ test('get', async () => {
})

test('get failed', async () => {
fetch.mockRejectedValue(new Error('mocked error'))
mockFetch.mockRejectedValue(new Error('mocked error'))
await expect(logForwarding.get()).rejects.toThrow("Could not get log forwarding settings for namespace 'some_namespace': mocked error")
})

test.each(dataFixtures)('set %s', async (destination, fnName, input) => {
return new Promise(resolve => {
fetch.mockReturnValue(new Promise(resolve => {
mockFetch.mockReturnValue(new Promise(resolve => {
resolve({
text: jest.fn().mockResolvedValue(`result for ${destination}`)
})
}))
return logForwarding[fnName](...Object.values(input))
.then((res) => {
expect(fetch).toBeCalledTimes(1)
expect(mockFetch).toBeCalledTimes(1)
expect(res).toBe(`result for ${destination}`)
assertRequest('put', { [destination]: input })
resolve()
Expand All @@ -67,14 +69,14 @@ test.each(dataFixtures)('set %s', async (destination, fnName, input) => {
})

test.each(dataFixtures)('set %s failed', async (destination, fnName, input) => {
fetch.mockRejectedValue(new Error(`mocked error for ${destination}`))
mockFetch.mockRejectedValue(new Error(`mocked error for ${destination}`))
await expect(logForwarding[fnName]())
.rejects
.toThrow(`Could not update log forwarding settings for namespace 'some_namespace': mocked error for ${destination}`)
})

const assertRequest = (expectedMethod, expectedData) => {
expect(fetch).toBeCalledWith(apiUrl, {
expect(mockFetch).toBeCalledWith(apiUrl, {
method: expectedMethod,
body: JSON.stringify(expectedData),
headers: {
Expand Down
9 changes: 6 additions & 3 deletions test/LogForwardingUnderscoreNamespace.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const fetch = require('cross-fetch')
const LogForwarding = require('../src/LogForwarding')

jest.mock('cross-fetch')
const { createFetch } = require('@adobe/aio-lib-core-networking')
const mockFetch = jest.fn()

jest.mock('@adobe/aio-lib-core-networking')

const dataFixtures = [
['adobe_io_runtime', 'setAdobeIoRuntime', {}],
Expand All @@ -22,7 +24,8 @@ let logForwarding

beforeEach(async () => {
logForwarding = new LogForwarding('_', 'host', 'key')
fetch.mockReset()
createFetch.mockReturnValue(mockFetch)
mockFetch.mockReset()
})

test('get for namespace "_" is not supported', async () => {
Expand Down
13 changes: 0 additions & 13 deletions test/build.actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,6 @@ describe('build by bundling js action file with webpack', () => {
'manifest.yml': global.fixtureFile('/sample-app/manifest.yml'),
'package.json': global.fixtureFile('/sample-app/package.json')
})
// mockAIOConfig.get.mockReturnValue(global.fakeConfig.tvm)
// scripts = await AppScripts()
// remove folder zip action , focus on bundled js use case
// todo use fixtures instead
/* vol.unlinkSync('/actions/action-zip/index.js')
vol.unlinkSync('/actions/action-zip/package.json')
vol.rmdirSync('/actions/action-zip') */
config = deepClone(global.sampleAppConfig)
// delete config.manifest.package.actions['action-zip']
delete config.manifest.full.packages.__APP_PACKAGE__.actions['action-zip']
Expand All @@ -239,12 +232,6 @@ describe('build by bundling js action file with webpack', () => {
await expect(buildActions(config)).rejects.toEqual(expect.objectContaining({ message: expect.stringContaining('ENOENT') }))
})

/* test('should fail if action js file is a symlink', async () => {
vol.unlinkSync('/actions/action.js')
vol.symlinkSync('somefile', '/actions/action.js')
await expect(buildActions(config)).rejects.toThrow('actions/action.js is not a valid file or directory')
}) */

test('should fail for invalid file or directory', async () => {
await buildActions(config)
expect(webpackMock.run).toHaveBeenCalledTimes(1)
Expand Down
Loading