This is an automated email from the ASF dual-hosted git repository.
rabbah pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new 70dd204 Script to generate IntelliJ Run Configuration for Controller
and Invoker (#3306)
70dd204 is described below
commit 70dd20452fec0c7d40681584a117521f94803ee4
Author: Chetan Mehrotra <[email protected]>
AuthorDate: Mon Mar 26 17:29:07 2018 +0530
Script to generate IntelliJ Run Configuration for Controller and Invoker
(#3306)
Script which generates the run configuration for Controller and Invoker
to enable launching them from within IDE. The env file may also be
reused in docker-compose.
It dumps 3 files
* build/env/whisk-common.env - Env values common to invoker and controller
* build/env/whisk-invoker.env - Env values specific to invoker
* build/env/whisk-controller.env - Env values specific to controller
---
.gitignore | 3 +
tools/dev/README.md | 69 ++++++
tools/dev/build.gradle | 13 +-
tools/dev/src/main/groovy/intellijRunConfig.groovy | 247 +++++++++++++++++++++
4 files changed, 331 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 960bb01..aab3af0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,3 +79,6 @@ ansible/roles/controller/files/*.p12
!tests/dat/actions/python_virtualenv_dir.zip
!tests/dat/actions/python_virtualenv_name.zip
!tests/dat/actions/zippedaction.zip
+
+# dev
+intellij-run-config.groovy
diff --git a/tools/dev/README.md b/tools/dev/README.md
index d270563..351a503 100644
--- a/tools/dev/README.md
+++ b/tools/dev/README.md
@@ -59,3 +59,72 @@ Sample output
Processing whisks_design_document_for_activations_db_filters_v2.1.0.json
- whisks-filters.v2.1.0-activations.js
Generated view json files in /path/too/tools/build/views
+
+## IntelliJ Run Config Generator
+
+This script enables creation of [Intellij Launch Configuration][1] in
_<openwhisk home>/.idea/runConfigurations_
+with name controller0 and invoker0. For this to work your Intellij project
should be [directory based][3]. If your
+project is file based (uses ipr files) then you can convert it to directory
based via _File -> Save as Directory-Based Format_. These run configurations
can then be invoked from _Run -> Edit Configurations -> Application_
+
+### Usage
+
+First setup OpenWhisk so that Controller and Invoker containers are up and
running. Then run the script:
+
+ ./gradlew -p tools/dev intellij
+
+It would inspect the running docker containers and then generate the launch
configs with name 'controller0'
+and 'invoker0'.
+
+Key points to note:
+
+1. Uses ~/tmp/openwhisk/controller (or invoker) as working directory.
+2. Changes the PORT to linked one. So controller gets started at 10001 only
just like as its done in container.
+
+Now the docker container can be stopped and application can be launched from
within the IDE.
+
+**Note** - Currently only the controller can be run from IDE. Invoker posses
some [problems][2].
+
+### Configuration
+
+The script allows some local customization of the launch configuration. This
can be done by creating a [config][4] file
+`intellij-run-config.groovy` in project root directory. Below is an example of
_<openwhisk home>/intellij-run-config.groovy_
+file to customize the logging and db port used for CouchDB.
+
+```groovy
+//Configures the settings for controller application
+controller {
+ //Base directory used for controller process
+ workingDir = "/path/to/controller"
+ //System properties to be set
+ props = [
+ 'logback.configurationFile':'/path/to/custom/logback.xml'
+ ]
+ //Environment variables to be set
+ env = [
+ 'DB_PORT' : '5989',
+ 'CONFIG_whisk_controller_protocol' : 'http'
+ ]
+}
+
+invoker {
+ workingDir = "/path/to/invoker"
+ props = [
+ 'logback.configurationFile':'/path/to/custom/logback.xml'
+ ]
+ env = [
+ 'DB_PORT' : '5989'
+ ]
+}
+
+```
+
+The config allows following properties:
+
+* `workingDir` - Base directory used for controller or invoker process.
+* `props` - Map of system properties which should be passed to the application.
+* `env` - Map of environment variables which should be set for application
process.
+
+[1]:
https://www.jetbrains.com/help/idea/run-debug-configurations-dialog.html#run_config_common_options
+[2]: https://github.com/apache/incubator-openwhisk/issues/3195
+[3]:
https://www.jetbrains.com/help/idea/configuring-projects.html#project-formats
+[4]: http://docs.groovy-lang.org/2.4.2/html/gapi/groovy/util/ConfigSlurper.html
diff --git a/tools/dev/build.gradle b/tools/dev/build.gradle
index af112a8..0dde40c 100644
--- a/tools/dev/build.gradle
+++ b/tools/dev/build.gradle
@@ -1,9 +1,13 @@
apply plugin: 'groovy'
+repositories {
+ mavenCentral()
+}
+
def owHome = project.projectDir.parentFile.parentFile
dependencies {
- compile localGroovy()
+ compile "org.codehaus.groovy:groovy-all:2.4.14"
}
task couchdbViews(type: JavaExec) {
@@ -12,3 +16,10 @@ task couchdbViews(type: JavaExec) {
args owHome.absolutePath
classpath = sourceSets.main.runtimeClasspath
}
+
+task intellij(type: JavaExec) {
+ description 'Generates Intellij run config for Controller and Invoker'
+ main = 'intellijRunConfig'
+ args owHome.absolutePath
+ classpath = sourceSets.main.runtimeClasspath
+}
diff --git a/tools/dev/src/main/groovy/intellijRunConfig.groovy
b/tools/dev/src/main/groovy/intellijRunConfig.groovy
new file mode 100644
index 0000000..f497b5e
--- /dev/null
+++ b/tools/dev/src/main/groovy/intellijRunConfig.groovy
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file 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 CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import groovy.json.JsonSlurper
+import groovy.text.SimpleTemplateEngine
+
+assert args : "Expecting the OpenWhisk home directory to passed"
+owHome = args[0]
+
+//Launch config template
+def configTemplate = '''<component name="ProjectRunConfigurationManager">
+ <configuration default="false" name="${name}" type="Application"
factoryName="Application">
+ <extension name="coverage" enabled="false" merge="false"
sample_coverage="true" runner="idea" />
+ <option name="MAIN_CLASS_NAME" value="$main" />
+ <option name="VM_PARAMETERS" value="$sysProps" />
+ <option name="PROGRAM_PARAMETERS" value="0" />
+ <option name="WORKING_DIRECTORY" value="$workingDir" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="ENABLE_SWING_INSPECTOR" value="false" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <module name="${type}_main" />
+ <envs>
+ <% env.each { k,v -> %><env name="$k" value="$v" />
+ <% } %>
+ </envs>
+ <method />
+ </configuration>
+</component>
+'''
+
+def meta = [
+ controller : [main:"whisk.core.controller.Controller"],
+ invoker : [main:"whisk.core.invoker.Invoker"]
+]
+
+//Get names of all running containers
+def containerNames = 'docker ps --format {{.Names}}'.execute().text.split("\n")
+
+config = getConfig()
+
+Map controllerEnv = null
+Map invokerEnv = null
+
+containerNames.each{cn ->
+ //Inspect the specific container
+ def inspectResult = "docker inspect $cn".execute().text
+ def json = new JsonSlurper().parseText(inspectResult)
+
+ def imageName = json[0].'Config'.'Image'
+ if (imageName.contains("controller") || imageName.contains("invoker")){
+ def mappedPort =
json.'NetworkSettings'.'Ports'.'8080/tcp'[0][0].'HostPort'
+
+ def envBaseMap = getEnvMap(json[0].'Config'.'Env')
+ String type
+ if (imageName.contains("controller")){
+ type = "controller"
+ controllerEnv = envBaseMap
+ } else {
+ type = "invoker"
+ invokerEnv = envBaseMap
+ }
+
+ def overrides = [
+ 'PORT' : mappedPort,
+ 'WHISK_LOGS_DIR' : "$owHome/core/$type/build/tmp"
+ ]
+
+ def envMap = getEnv(envBaseMap, type, overrides)
+
+ //Prepare system properties
+ def sysProps = getSysProps(envMap,type)
+
+ def templateBinding = [
+ main: meta[type].main,
+ type:type,
+ name:cn,
+ env: encodeForXML(envMap),
+ sysProps : sysProps,
+ USER_HOME : '$USER_HOME$',
+ workingDir : getWorkDir(type)
+ ]
+
+ def engine = new SimpleTemplateEngine()
+ def template =
engine.createTemplate(configTemplate).make(templateBinding)
+
+ def launchFile = new File("$owHome/.idea/runConfigurations/${cn}.xml")
+ launchFile.parentFile.mkdirs()
+ launchFile.text = template
+ println "Created ${launchFile.absolutePath}"
+ }
+}
+
+/**
+ * Computes the env values which are common and then specific to controller
and invoker
+ * and dumps them to a file. This can be used for docker-compose
+ */
+if (controllerEnv != null && invokerEnv != null){
+ Set<String> commonKeys =
controllerEnv.keySet().intersect(invokerEnv.keySet())
+
+ SortedMap commonEnv = new TreeMap()
+ SortedMap controllerSpecificEnv = new TreeMap(controllerEnv)
+ SortedMap invokerSpecificEnv = new TreeMap(invokerEnv)
+ commonKeys.each{ key ->
+ if (controllerEnv[key] == invokerEnv[key]){
+ commonEnv[key] = controllerEnv[key]
+ controllerSpecificEnv.remove(key)
+ invokerSpecificEnv.remove(key)
+ }
+ }
+
+ copyEnvToFile(commonEnv,"whisk-common.env")
+ copyEnvToFile(controllerSpecificEnv,"whisk-controller.env")
+ copyEnvToFile(invokerSpecificEnv,"whisk-invoker.env")
+}
+
+def copyEnvToFile(SortedMap envMap,String envFileName){
+ File envFile = new File(getEnvFileDir(), envFileName)
+ envFile.withPrintWriter {pw ->
+ envMap.each{k,v ->
+ pw.println("$k=$v")
+
+ }
+ }
+ println "Wrote env to ${envFile.absolutePath}"
+}
+
+private File getEnvFileDir() {
+ File dir = new File(new File("build"), "env")
+ dir.mkdirs()
+ return dir
+}
+
+/**
+ * Reads config from intellij-run-config.groovy file
+ */
+def getConfig(){
+ def configFile = new File(owHome, 'intellij-run-config.groovy')
+ def config = configFile.exists() ? new
ConfigSlurper().parse(configFile.text) : new ConfigObject()
+ if (configFile.exists()) {
+ println "Reading config from ${configFile.absolutePath}"
+ }
+ config
+}
+
+def getWorkDir(String type){
+ def dir = config[type].workingDir
+ if (dir){
+ File f = new File(dir)
+ if (!f.exists()) {
+ f.mkdirs()
+ }
+ dir
+ }else {
+ 'file://$MODULE_DIR$'
+ }
+}
+
+def getSysProps(def envMap, String type){
+ def props = config[type].props
+ def sysProps = transformEnv(envMap)
+ sysProps.putAll(props)
+ sysProps.collect{k,v -> "-D$k='$v'"}.join(' ').replace('\'','')
+}
+
+//Implements the logic from transformEnvironment.sh
+//to ensure comparability as sed -r is not supported on Mac
+def transformEnv(Map<String, String> envMap){
+ def transformedMap = [:]
+ envMap.each{String k,String v ->
+ if (!k.startsWith("CONFIG_") || v.isEmpty()) return
+ k = k.substring("CONFIG_".length())
+ def parts = k.split("\\_")
+ def transformedKey = parts.collect {p ->
+ if (Character.isUpperCase(p[0] as char)){
+ // if the current part starts with an uppercase letter (is
PascalCased)
+ // leave it alone
+ return p
+ } else {
+ // rewrite camelCased to kebab-cased
+ return p.replaceAll(/([a-z0-9])([A-Z])/,/$1-$2/).toLowerCase()
+ }
+ }
+
+ //Resolve values which again refer to env variables
+ if (v.startsWith('$')) {
+ def valueAsKey = v.substring(1)
+ if (envMap.containsKey(valueAsKey)){
+ v = envMap.get(valueAsKey)
+ }
+ }
+ transformedMap[transformedKey.join('.')] = v
+ }
+ return transformedMap
+}
+
+/**
+ * Inspect command from docker returns the environment variables as list of
string of form key=value
+ * This method converts it to map and add provided overrides with overrides
from config
+ */
+def getEnv(Map envMap, String type, Map overrides){
+ def ignoredKeys = ['PATH']
+ def overridesFromConfig = config[type].env
+ Map sortedMap = new TreeMap(envMap)
+ sortedMap.putAll(overrides)
+
+ //Config override come last
+ sortedMap.putAll(overridesFromConfig)
+
+ //Remove ignored keys like PATH which should be inherited
+ ignoredKeys.each {sortedMap.remove(it)}
+ sortedMap
+}
+
+def getEnvMap(def env){
+ def envMap = env.collectEntries {String e ->
+ def eqIndex = e.indexOf('=')
+ def k = e.substring(0, eqIndex)
+ def v = e.substring(eqIndex + 1)
+ [(k):v]
+ }
+ def sortedMap = new TreeMap()
+ sortedMap.putAll(envMap)
+ Collections.unmodifiableSortedMap(sortedMap)
+}
+
+def getEnvAsList(Map envMap){
+ envMap.collect{k,v -> "$k=$v"}
+}
+
+def encodeForXML(Map map){
+ map.collectEntries {k,v -> [(k): v.replace('"', '"')]}
+}
--
To stop receiving notification emails like this one, please contact
[email protected].