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

Added support to build and run UI only apps. #80

Merged
merged 9 commits into from
Mar 6, 2020
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
14 changes: 9 additions & 5 deletions lib/config-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,16 @@ module.exports = () => {
// todo we shouldn't have any config.web config if !hasFrontend
config.app.hasFrontend = fs.existsSync(path.join(config.web.src, 'index.html'))

// check if the app has a backend by checking presence of manifest.yml file
config.app.hasBackend = fs.existsSync(config.manifest.src)

// todo change env var to DEV_LOCAL_ACTIONS because REMOTE_ACTIONS is only used in the context of dev cmd
// this creates confusion as for other commands actions are always remote although REMOTE_ACTIONS is not set
const remoteString = process.env.REMOTE_ACTIONS
config.actions.devRemote = remoteString === true || remoteString === 'true' || remoteString === 'yes' || remoteString === '1'

// 2. check needed files
aioLogger.debug('checking manifest and package.json existence')
utils.checkFile(config.manifest.src)
aioLogger.debug('checking package.json existence')
utils.checkFile(_abs('package.json'))

// 3. load app config from package.json
Expand All @@ -131,9 +133,11 @@ module.exports = () => {
config.app.name = getModuleName(packagejson) || 'unnamed-cna'

// 4. Load manifest config
config.manifest.packagePlaceholder = '__APP_PACKAGE__'
config.manifest.full = yaml.safeLoad(fs.readFileSync(config.manifest.src, 'utf8'))
config.manifest.package = config.manifest.full.packages[config.manifest.packagePlaceholder]
if (config.app.hasBackend) {
config.manifest.packagePlaceholder = '__APP_PACKAGE__'
config.manifest.full = yaml.safeLoad(fs.readFileSync(config.manifest.src, 'utf8'))
config.manifest.package = config.manifest.full.packages[config.manifest.packagePlaceholder]
}

// 5. deployment config
config.ow = userConfig.runtime || {}
Expand Down
1 change: 1 addition & 0 deletions scripts/build.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class BuildActions extends BaseScript {
* @memberof DeployActions
*/
async run (args = [], buildConfig = {}) {
if (!this.config.app.hasBackend) throw new Error('cannot build actions, app has no backend')
const taskName = 'Build actions'
this.emit('start', taskName)

Expand Down
3 changes: 2 additions & 1 deletion scripts/build.ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class BuildUI extends BaseScript {
this.emit('warning', 'injected urls to backend actions are invalid because of missing Adobe I/O Runtime apihost and/or namespace')
}

const urls = await utils.getActionUrls(this.config)
let urls = {}
if (this.config.app.hasBackend) { urls = await utils.getActionUrls(this.config) }

await utils.writeConfig(this.config.web.injectedConfig, urls)

Expand Down
5 changes: 4 additions & 1 deletion scripts/deploy.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ class DeployActions extends BaseScript {
* @memberof DeployActions
*/
async run (args = [], deployConfig = {}) {
if (!this.config.app.hasBackend) throw new Error('cannot deploy actions, app has no backend')
const taskName = 'Deploy actions'
this.emit('start', taskName)

const isLocalDev = deployConfig.isLocalDev

// checks
/// a. missing credentials
utils.checkOpenWhiskCredentials(this.config)
Expand Down Expand Up @@ -87,7 +90,7 @@ class DeployActions extends BaseScript {

// enrich actions array with urls
if (Array.isArray(deployedEntities.actions)) {
const actionUrlsFromManifest = utils.getActionUrls(this.config)
const actionUrlsFromManifest = utils.getActionUrls(this.config, this.config.actions.devRemote, isLocalDev)
deployedEntities.actions = deployedEntities.actions.map(a => {
// in deployedEntities.actions, names are <package>/<action>
const url = actionUrlsFromManifest[a.name.split('/')[1]]
Expand Down
225 changes: 116 additions & 109 deletions scripts/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ class ActionServer extends BaseScript {
const CODE_DEBUG = this._absApp('.vscode/launch.json')

// control variables
const isLocal = !this.config.actions.devRemote
const hasFrontend = this.config.app.hasFrontend
const hasBackend = this.config.app.hasBackend
const isLocal = !this.config.actions.devRemote // applies only for backend

// todo take port for ow server as well
// port for UI
Expand All @@ -65,77 +66,80 @@ class ActionServer extends BaseScript {

// state
const resources = {}
let devConfig // config will be different if local or remote
let devConfig = this.config // config will be different if local or remote

// bind cleanup function
process.on('SIGINT', () => cleanup(null, resources))

try {
if (isLocal) {
this.emit('progress', 'checking if java is installed...')
if (!await utils.hasJavaCLI()) throw new Error('could not find java CLI, please make sure java is installed')

this.emit('progress', 'checking if docker is installed...')
if (!await utils.hasDockerCLI()) throw new Error('could not find docker CLI, please make sure docker is installed')

this.emit('progress', 'checking if docker is running...')
if (!await utils.isDockerRunning()) throw new Error('docker is not running, please make sure to start docker')

if (!fs.existsSync(OW_JAR_FILE)) {
this.emit('progress', `downloading OpenWhisk standalone jar from ${OW_JAR_URL} to ${OW_JAR_FILE}, this might take a while... (to be done only once!)`)
await utils.downloadOWJar(OW_JAR_URL, OW_JAR_FILE)
}

this.emit('progress', 'starting local OpenWhisk stack..')
const res = await utils.runOpenWhiskJar(OW_JAR_FILE, OW_LOCAL_APIHOST, owWaitInitTime, owWaitPeriodTime, owTimeout, { stderr: 'inherit' })
resources.owProc = res.proc

// case1: no dotenv file => expose local credentials in .env, delete on cleanup
const dotenvFile = this._absApp('.env')
if (!fs.existsSync(dotenvFile)) {
// todo move to utils
this.emit('progress', 'writing temporary .env with local OpenWhisk guest credentials..')
fs.writeFileSync(dotenvFile, `AIO_RUNTIME_NAMESPACE=${OW_LOCAL_NAMESPACE}\nAIO_RUNTIME_AUTH=${OW_LOCAL_AUTH}\nAIO_RUNTIME_APIHOST=${OW_LOCAL_APIHOST}`)
resources.dotenv = dotenvFile
if (hasBackend) {
if (isLocal) {
// take following steps only when we have a backend
this.emit('progress', 'checking if java is installed...')
if (!await utils.hasJavaCLI()) throw new Error('could not find java CLI, please make sure java is installed')

this.emit('progress', 'checking if docker is installed...')
if (!await utils.hasDockerCLI()) throw new Error('could not find docker CLI, please make sure docker is installed')

this.emit('progress', 'checking if docker is running...')
if (!await utils.isDockerRunning()) throw new Error('docker is not running, please make sure to start docker')

if (!fs.existsSync(OW_JAR_FILE)) {
this.emit('progress', `downloading OpenWhisk standalone jar from ${OW_JAR_URL} to ${OW_JAR_FILE}, this might take a while... (to be done only once!)`)
await utils.downloadOWJar(OW_JAR_URL, OW_JAR_FILE)
}

this.emit('progress', 'starting local OpenWhisk stack..')
const res = await utils.runOpenWhiskJar(OW_JAR_FILE, OW_LOCAL_APIHOST, owWaitInitTime, owWaitPeriodTime, owTimeout, { stderr: 'inherit' })
resources.owProc = res.proc

// case1: no dotenv file => expose local credentials in .env, delete on cleanup
const dotenvFile = this._absApp('.env')
if (!fs.existsSync(dotenvFile)) {
// todo move to utils
this.emit('progress', 'writing temporary .env with local OpenWhisk guest credentials..')
fs.writeFileSync(dotenvFile, `AIO_RUNTIME_NAMESPACE=${OW_LOCAL_NAMESPACE}\nAIO_RUNTIME_AUTH=${OW_LOCAL_AUTH}\nAIO_RUNTIME_APIHOST=${OW_LOCAL_APIHOST}`)
resources.dotenv = dotenvFile
} else {
// case2: existing dotenv file => save .env & expose local credentials in .env, restore on cleanup
this.emit('progress', `saving .env to ${DOTENV_SAVE} and writing new .env with local OpenWhisk guest credentials..`)
utils.saveAndReplaceDotEnvCredentials(dotenvFile, DOTENV_SAVE, OW_LOCAL_APIHOST, OW_LOCAL_NAMESPACE, OW_LOCAL_AUTH)
resources.dotenvSave = DOTENV_SAVE
resources.dotenv = dotenvFile
}
// delete potentially conflicting env vars
delete process.env.AIO_RUNTIME_APIHOST
delete process.env.AIO_RUNTIME_NAMESPACE
delete process.env.AIO_RUNTIME_AUTH

devConfig = require('../lib/config-loader')() // reload config for local config
} else {
// case2: existing dotenv file => save .env & expose local credentials in .env, restore on cleanup
this.emit('progress', `saving .env to ${DOTENV_SAVE} and writing new .env with local OpenWhisk guest credentials..`)
utils.saveAndReplaceDotEnvCredentials(dotenvFile, DOTENV_SAVE, OW_LOCAL_APIHOST, OW_LOCAL_NAMESPACE, OW_LOCAL_AUTH)
resources.dotenvSave = DOTENV_SAVE
resources.dotenv = dotenvFile
// check credentials
utils.checkOpenWhiskCredentials(this.config)
this.emit('progress', 'using remote actions')
}
// delete potentially conflicting env vars
delete process.env.AIO_RUNTIME_APIHOST
delete process.env.AIO_RUNTIME_NAMESPACE
delete process.env.AIO_RUNTIME_AUTH

devConfig = require('../lib/config-loader')() // reload config for local config
} else {
// check credentials
utils.checkOpenWhiskCredentials(this.config)
this.emit('progress', 'using remote actions')
devConfig = this.config
}

// build and deploy actions
// todo support live reloading ?
this.emit('progress', 'redeploying actions..')
await this._buildAndDeploy(devConfig)
// build and deploy actions
// todo support live reloading ?
this.emit('progress', 'redeploying actions..')
await this._buildAndDeploy(devConfig, isLocal)

watcher = chokidar.watch(devConfig.actions.src)
watcher.on('change', this._getActionChangeHandler(devConfig))
watcher = chokidar.watch(devConfig.actions.src)
watcher.on('change', this._getActionChangeHandler(devConfig, isLocal))

this.emit('progress', `writing credentials to tmp wskdebug config '${this._relApp(WSK_DEBUG_PROPS)}'..`)
// prepare wskprops for wskdebug
fs.writeFileSync(WSK_DEBUG_PROPS, `NAMESPACE=${devConfig.ow.namespace}\nAUTH=${devConfig.ow.auth}\nAPIHOST=${devConfig.ow.apihost}`)
resources.wskdebugProps = WSK_DEBUG_PROPS
this.emit('progress', `writing credentials to tmp wskdebug config '${this._relApp(WSK_DEBUG_PROPS)}'..`)
// prepare wskprops for wskdebug
fs.writeFileSync(WSK_DEBUG_PROPS, `NAMESPACE=${devConfig.ow.namespace}\nAUTH=${devConfig.ow.auth}\nAPIHOST=${devConfig.ow.apihost}`)
resources.wskdebugProps = WSK_DEBUG_PROPS
}

if (hasFrontend) {
Copy link
Member

Choose a reason for hiding this comment

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

also if not hasBackend we don't want to build & deploy actions + don't need to start the openwhisk server

// inject backend urls into ui
this.emit('progress', 'injecting backend urls into frontend config')

const urls = await utils.getActionUrls(devConfig, true, isLocal)

let urls = {}
if (devConfig.app.hasBackend) {
// inject backend urls into ui
this.emit('progress', 'injecting backend urls into frontend config')
urls = await utils.getActionUrls(devConfig, true, isLocal)
}
await utils.writeConfig(devConfig.web.injectedConfig, urls)

this.emit('progress', 'starting local frontend server ..')
Expand Down Expand Up @@ -198,53 +202,56 @@ class ActionServer extends BaseScript {

// todo make util not instance function
async generateVSCodeDebugConfig (devConfig, hasFrontend, frontUrl, wskdebugProps) {
const packageName = devConfig.ow.package
const manifestActions = devConfig.manifest.package.actions

const actionConfigNames = []
const actionConfigs = Object.keys(manifestActions).map(an => {
const name = `Action:${packageName}/${an}`
actionConfigNames.push(name)
const action = manifestActions[an]
const actionPath = this._absApp(action.function)

const config = {
type: 'node',
request: 'launch',
name: name,
// todo allow for global install aswell
runtimeExecutable: this._absApp('./node_modules/.bin/wskdebug'),
env: { WSK_CONFIG_FILE: wskdebugProps },
timeout: 30000,
// replaces remoteRoot with localRoot to get src files
localRoot: this._absApp('.'),
remoteRoot: '/code',
outputCapture: 'std'
}
let actionConfigs = []
if (devConfig.app.hasBackend) {
const packageName = devConfig.ow.package
const manifestActions = devConfig.manifest.package.actions

actionConfigs = Object.keys(manifestActions).map(an => {
const name = `Action:${packageName}/${an}`
actionConfigNames.push(name)
const action = manifestActions[an]
const actionPath = this._absApp(action.function)

const config = {
type: 'node',
request: 'launch',
name: name,
// todo allow for global install aswell
runtimeExecutable: this._absApp('./node_modules/.bin/wskdebug'),
env: { WSK_CONFIG_FILE: wskdebugProps },
timeout: 30000,
// replaces remoteRoot with localRoot to get src files
localRoot: this._absApp('.'),
remoteRoot: '/code',
outputCapture: 'std'
}

const actionFileStats = fs.lstatSync(actionPath)
if (actionFileStats.isFile()) {
// why is this condition here?
}
if (actionFileStats.isDirectory()) {
// take package.json.main or 'index.js'
const zipMain = utils.getActionEntryFile(path.join(actionPath, 'package.json'))
config.runtimeArgs = [
`${packageName}/${an}`,
path.join(actionPath, zipMain),
'-v'
]
} else {
// we assume its a file at this point
// if symlink should have thrown an error during build stage, here we just ignore it
config.runtimeArgs = [
`${packageName}/${an}`,
actionPath,
'-v'
]
}
return config
})
const actionFileStats = fs.lstatSync(actionPath)
if (actionFileStats.isFile()) {
// why is this condition here?
}
if (actionFileStats.isDirectory()) {
// take package.json.main or 'index.js'
const zipMain = utils.getActionEntryFile(path.join(actionPath, 'package.json'))
config.runtimeArgs = [
`${packageName}/${an}`,
path.join(actionPath, zipMain),
'-v'
]
} else {
// we assume its a file at this point
// if symlink should have thrown an error during build stage, here we just ignore it
config.runtimeArgs = [
`${packageName}/${an}`,
actionPath,
'-v'
]
}
return config
})
}
const debugConfig = {
configurations: actionConfigs,
compounds: [{
Expand Down Expand Up @@ -272,7 +279,7 @@ class ActionServer extends BaseScript {
return debugConfig
}

_getActionChangeHandler (devConfig) {
_getActionChangeHandler (devConfig, isLocalDev) {
return async (filePath) => {
if (running) {
aioLogger.debug(`${filePath} has changed. Deploy in progress. This change will be deployed after completion of current deployment.`)
Expand All @@ -282,7 +289,7 @@ class ActionServer extends BaseScript {
running = true
try {
aioLogger.debug(`${filePath} has changed. Redeploying actions.`)
await this._buildAndDeploy(devConfig)
await this._buildAndDeploy(devConfig, isLocalDev)
aioLogger.debug('Deployment successfull.')
} catch (err) {
this.emit('progress', ' -> Error encountered while deploying actions. Stopping auto refresh.')
Expand All @@ -298,9 +305,9 @@ class ActionServer extends BaseScript {
}
}

async _buildAndDeploy (devConfig) {
async _buildAndDeploy (devConfig, isLocalDev) {
await (new BuildActions(devConfig)).run()
const entities = await (new DeployActions(devConfig)).run()
const entities = await (new DeployActions(devConfig)).run([], { isLocalDev })
if (entities.actions) {
entities.actions.forEach(a => {
this.emit('progress', ` -> ${a.url || a.name}`)
Expand Down
2 changes: 2 additions & 0 deletions scripts/undeploy.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const OpenWhisk = require('openwhisk')

class UndeployActions extends BaseScript {
async run () {
if (!this.config.app.hasBackend) throw new Error('cannot undeploy actions, app has no backend')

const taskName = 'Undeploy actions'
this.emit('start', taskName)

Expand Down
10 changes: 6 additions & 4 deletions test/common.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,19 @@ test('Load AppScripts for valid app in creds mode, and should store them in inte
expect(scripts._config.s3.creds).toEqual(global.expectedS3ENVCreds)
})

test('Fail load AppScripts with missing manifest.yml', async () => {
test('Load AppScripts with missing manifest.yml', async () => {
mockAIOConfig.get.mockReturnValue(global.fakeConfig.tvm)
fs.unlinkSync(path.join(process.cwd(), 'manifest.yml'))
expect(AppScripts.bind(this)).toThrowWithMessageContaining(['no such file', 'manifest.yml'])
const scripts = AppScripts()
expect(scripts).toEqual(global.expectedScripts)
})

test('Fail load AppScripts with symlink manifest.yml', async () => {
test('Load AppScripts with symlink manifest.yml', async () => {
mockAIOConfig.get.mockReturnValue(global.fakeConfig.tvm)
fs.unlinkSync('/manifest.yml')
fs.symlinkSync('fake', '/manifest.yml')
expect(AppScripts.bind(this)).toThrowWithMessageContaining([`${r('/manifest.yml')} is not a valid file (e.g. cannot be a dir or a symlink)`])
const scripts = AppScripts()
expect(scripts).toEqual(global.expectedScripts)
})

test('Fail load AppScripts with missing package.json', async () => {
Expand Down
Loading