You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by mr...@apache.org on 2018/04/02 21:14:22 UTC

[incubator-openwhisk-wskdeploy] branch master updated: Adding support for conductor, web sequences, and converting to web actions for api gateway (#834)

This is an automated email from the ASF dual-hosted git repository.

mrutkowski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-wskdeploy.git


The following commit(s) were added to refs/heads/master by this push:
     new 4ee25af  Adding support for conductor, web sequences, and converting to web actions for api gateway (#834)
4ee25af is described below

commit 4ee25af848742c50276b764cac5b6d001891d34f
Author: Priti Desai <pd...@us.ibm.com>
AuthorDate: Mon Apr 2 14:14:18 2018 -0700

    Adding support for conductor, web sequences, and converting to web actions for api gateway (#834)
    
    * adding conductor support, web sequence and web action in addition to webexport
    
    * converting actions to web action if its not specified with api gateway
    
    * adding newline
    
    * fixing manifest file name
    
    * adding integration on conductor
    
    * adding newline
---
 deployers/manifestreader.go                        |   2 +-
 parsers/manifest_parser.go                         | 159 ++++++++++++---------
 parsers/manifest_parser_test.go                    |  40 +++++-
 parsers/yamlparser.go                              |  12 ++
 ...ata_compose_actions_for_web_and_web_export.yaml |  39 +++++
 tests/dat/manifest_data_compose_api_records.yaml   |   5 -
 tests/src/integration/apigateway/manifest.yml      |   5 -
 .../src/integration/conductor/actions/increment.js |  21 +++
 tests/src/integration/conductor/actions/triple.js  |  21 +++
 .../conductor/actions/tripleAndIncrement.js        |  27 ++++
 tests/src/integration/conductor/conductor_test.go  |  40 ++++++
 tests/src/integration/conductor/manifest.yaml      |  52 +++++++
 tests/src/integration/websequence/manifest.yaml    |  50 +++++++
 tests/src/integration/websequence/src/greeting.js  |  32 +++++
 .../integration/websequence/websequence_test.go    |  43 ++++++
 utils/conductor.go                                 |  29 ++++
 utils/conductor_test.go                            |  37 +++++
 utils/misc.go                                      |  42 +-----
 utils/webaction.go                                 |   9 +-
 wski18n/i18n_ids.go                                |   3 +-
 wski18n/i18n_resources.go                          |   4 +-
 wski18n/resources/en_US.all.json                   |  12 +-
 22 files changed, 559 insertions(+), 125 deletions(-)

diff --git a/deployers/manifestreader.go b/deployers/manifestreader.go
index c9f0268..1f9171d 100644
--- a/deployers/manifestreader.go
+++ b/deployers/manifestreader.go
@@ -78,7 +78,7 @@ func (deployer *ManifestReader) HandleYaml(sdeployer *ServiceDeployer, manifestP
 		return wskderrors.NewYAMLFileFormatError(manifestName, err)
 	}
 
-	sequences, err := manifestParser.ComposeSequencesFromAllPackages(deployer.serviceDeployer.ClientConfig.Namespace, manifest, managedAnnotations)
+	sequences, err := manifestParser.ComposeSequencesFromAllPackages(deployer.serviceDeployer.ClientConfig.Namespace, manifest, deployer.serviceDeployer.ManifestPath, managedAnnotations)
 	if err != nil {
 		return wskderrors.NewYAMLFileFormatError(manifestName, err)
 	}
diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go
index 9cfa673..b46f73a 100644
--- a/parsers/manifest_parser.go
+++ b/parsers/manifest_parser.go
@@ -142,7 +142,7 @@ func (dm *YAMLParser) composeAnnotations(annotations map[string]interface{}) whi
 	return listOfAnnotations
 }
 
-func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, projectPath string, filePath string, ma whisk.KeyValue) (map[string]utils.DependencyRecord, error) {
+func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, projectPath string, filePath string, managedAnnotations whisk.KeyValue) (map[string]utils.DependencyRecord, error) {
 	dependencies := make(map[string]utils.DependencyRecord)
 	packages := make(map[string]Package)
 
@@ -153,7 +153,7 @@ func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, project
 	}
 
 	for n, p := range packages {
-		d, err := dm.ComposeDependencies(p, projectPath, filePath, n, ma)
+		d, err := dm.ComposeDependencies(p, projectPath, filePath, n, managedAnnotations)
 		if err == nil {
 			for k, v := range d {
 				dependencies[k] = v
@@ -165,7 +165,7 @@ func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, project
 	return dependencies, nil
 }
 
-func (dm *YAMLParser) ComposeDependencies(pkg Package, projectPath string, filePath string, packageName string, ma whisk.KeyValue) (map[string]utils.DependencyRecord, error) {
+func (dm *YAMLParser) ComposeDependencies(pkg Package, projectPath string, filePath string, packageName string, managedAnnotations whisk.KeyValue) (map[string]utils.DependencyRecord, error) {
 
 	depMap := make(map[string]utils.DependencyRecord)
 	for key, dependency := range pkg.Dependencies {
@@ -203,7 +203,7 @@ func (dm *YAMLParser) ComposeDependencies(pkg Package, projectPath string, fileP
 		annotations := dm.composeAnnotations(dependency.Annotations)
 
 		if utils.Flags.Managed || utils.Flags.Sync {
-			annotations = append(annotations, ma)
+			annotations = append(annotations, managedAnnotations)
 		}
 
 		packDir := path.Join(projectPath, strings.Title(YAML_KEY_PACKAGES))
@@ -214,7 +214,7 @@ func (dm *YAMLParser) ComposeDependencies(pkg Package, projectPath string, fileP
 	return depMap, nil
 }
 
-func (dm *YAMLParser) ComposeAllPackages(manifest *YAML, filePath string, ma whisk.KeyValue) (map[string]*whisk.Package, error) {
+func (dm *YAMLParser) ComposeAllPackages(manifest *YAML, filePath string, managedAnnotations whisk.KeyValue) (map[string]*whisk.Package, error) {
 	packages := map[string]*whisk.Package{}
 	manifestPackages := make(map[string]Package)
 
@@ -234,7 +234,7 @@ func (dm *YAMLParser) ComposeAllPackages(manifest *YAML, filePath string, ma whi
 
 	// Compose each package found in manifest
 	for n, p := range manifestPackages {
-		s, err := dm.ComposePackage(p, n, filePath, ma)
+		s, err := dm.ComposePackage(p, n, filePath, managedAnnotations)
 
 		if err == nil {
 			packages[n] = s
@@ -246,7 +246,7 @@ func (dm *YAMLParser) ComposeAllPackages(manifest *YAML, filePath string, ma whi
 	return packages, nil
 }
 
-func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath string, ma whisk.KeyValue) (*whisk.Package, error) {
+func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath string, managedAnnotations whisk.KeyValue) (*whisk.Package, error) {
 	pag := &whisk.Package{}
 	pag.Name = packageName
 	//The namespace for this package is absent, so we use default guest here.
@@ -314,7 +314,7 @@ func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath s
 
 	// add Managed Annotations if this is Managed Deployment
 	if utils.Flags.Managed || utils.Flags.Sync {
-		pag.Annotations = append(pag.Annotations, ma)
+		pag.Annotations = append(pag.Annotations, managedAnnotations)
 	}
 
 	// "default" package is a reserved package name
@@ -336,7 +336,7 @@ func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath s
 	return pag, nil
 }
 
-func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *YAML, ma whisk.KeyValue) ([]utils.ActionRecord, error) {
+func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *YAML, manifestFilePath string, managedAnnotations whisk.KeyValue) ([]utils.ActionRecord, error) {
 	var sequences []utils.ActionRecord = make([]utils.ActionRecord, 0)
 	manifestPackages := make(map[string]Package)
 
@@ -347,7 +347,7 @@ func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *YA
 	}
 
 	for n, p := range manifestPackages {
-		s, err := dm.ComposeSequences(namespace, p.Sequences, n, ma)
+		s, err := dm.ComposeSequences(namespace, p.Sequences, n, manifestFilePath, managedAnnotations)
 		if err == nil {
 			sequences = append(sequences, s...)
 		} else {
@@ -357,8 +357,9 @@ func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *YA
 	return sequences, nil
 }
 
-func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Sequence, packageName string, ma whisk.KeyValue) ([]utils.ActionRecord, error) {
+func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Sequence, packageName string, manifestFilePath string, managedAnnotations whisk.KeyValue) ([]utils.ActionRecord, error) {
 	var listOfSequences []utils.ActionRecord = make([]utils.ActionRecord, 0)
+	var errorParser error
 
 	for key, sequence := range sequences {
 		wskaction := new(whisk.Action)
@@ -389,7 +390,19 @@ func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Se
 
 		// appending managed annotations if its a managed deployment
 		if utils.Flags.Managed || utils.Flags.Sync {
-			wskaction.Annotations = append(wskaction.Annotations, ma)
+			wskaction.Annotations = append(wskaction.Annotations, managedAnnotations)
+		}
+
+		// Web Export
+		// Treat sequence as a web action, a raw HTTP web action, or as a standard action based on web-export;
+		// when web-export is set to yes | true, treat sequence as a web action,
+		// when web-export is set to raw, treat sequence as a raw HTTP web action,
+		// when web-export is set to no | false, treat sequence as a standard action
+		if len(sequence.Web) != 0 {
+			wskaction.Annotations, errorParser = utils.WebAction(manifestFilePath, wskaction.Name, sequence.Web, wskaction.Annotations, false)
+			if errorParser != nil {
+				return nil, errorParser
+			}
 		}
 
 		record := utils.ActionRecord{Action: wskaction, Packagename: packageName, Filepath: key}
@@ -398,7 +411,7 @@ func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Se
 	return listOfSequences, nil
 }
 
-func (dm *YAMLParser) ComposeActionsFromAllPackages(manifest *YAML, filePath string, ma whisk.KeyValue) ([]utils.ActionRecord, error) {
+func (dm *YAMLParser) ComposeActionsFromAllPackages(manifest *YAML, filePath string, managedAnnotations whisk.KeyValue) ([]utils.ActionRecord, error) {
 	var actions []utils.ActionRecord = make([]utils.ActionRecord, 0)
 	manifestPackages := make(map[string]Package)
 
@@ -409,7 +422,7 @@ func (dm *YAMLParser) ComposeActionsFromAllPackages(manifest *YAML, filePath str
 	}
 
 	for n, p := range manifestPackages {
-		a, err := dm.ComposeActions(filePath, p.Actions, n, ma)
+		a, err := dm.ComposeActions(filePath, p.Actions, n, managedAnnotations)
 		if err == nil {
 			actions = append(actions, a...)
 		} else {
@@ -708,7 +721,15 @@ func (dm *YAMLParser) composeActionLimits(limits Limits) *whisk.Limits {
 	return nil
 }
 
-func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string]Action, packageName string, ma whisk.KeyValue) ([]utils.ActionRecord, error) {
+func (dm *YAMLParser) validateActionWebFlag(action Action) {
+	if len(action.Web) != 0 && len(action.Webexport) != 0 {
+		warningString := wski18n.T(wski18n.ID_WARN_ACTION_WEB_X_action_X,
+			map[string]interface{}{wski18n.KEY_ACTION: action.Name})
+		wskprint.PrintOpenWhiskWarning(warningString)
+	}
+}
+
+func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string]Action, packageName string, managedAnnotations whisk.KeyValue) ([]utils.ActionRecord, error) {
 
 	var errorParser error
 	var listOfActions []utils.ActionRecord = make([]utils.ActionRecord, 0)
@@ -762,7 +783,7 @@ func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string
 
 		// add managed annotations if its marked as managed deployment
 		if utils.Flags.Managed || utils.Flags.Sync {
-			wskaction.Annotations = append(wskaction.Annotations, ma)
+			wskaction.Annotations = append(wskaction.Annotations, managedAnnotations)
 		}
 
 		// Web Export
@@ -770,8 +791,9 @@ func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string
 		// when web-export is set to yes | true, treat action as a web action,
 		// when web-export is set to raw, treat action as a raw HTTP web action,
 		// when web-export is set to no | false, treat action as a standard action
-		if len(action.Webexport) != 0 {
-			wskaction.Annotations, errorParser = utils.WebAction(manifestFilePath, action.Name, action.Webexport, wskaction.Annotations, false)
+		dm.validateActionWebFlag(action)
+		if len(action.GetWeb()) != 0 {
+			wskaction.Annotations, errorParser = utils.WebAction(manifestFilePath, action.Name, action.GetWeb(), wskaction.Annotations, false)
 			if errorParser != nil {
 				return listOfActions, errorParser
 			}
@@ -783,6 +805,10 @@ func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string
 				wskaction.Limits = wsklimits
 			}
 		}
+		// Conductor Action
+		if action.Conductor {
+			wskaction.Annotations = append(wskaction.Annotations, utils.ConductorAction())
+		}
 
 		wskaction.Name = actionName
 		pub := false
@@ -796,7 +822,7 @@ func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string
 
 }
 
-func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *YAML, filePath string, ma whisk.KeyValue) ([]*whisk.Trigger, error) {
+func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *YAML, filePath string, managedAnnotations whisk.KeyValue) ([]*whisk.Trigger, error) {
 	var triggers []*whisk.Trigger = make([]*whisk.Trigger, 0)
 	manifestPackages := make(map[string]Package)
 
@@ -807,7 +833,7 @@ func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *YAML, filePath st
 	}
 
 	for _, p := range manifestPackages {
-		t, err := dm.ComposeTriggers(filePath, p, ma)
+		t, err := dm.ComposeTriggers(filePath, p, managedAnnotations)
 		if err == nil {
 			triggers = append(triggers, t...)
 		} else {
@@ -817,7 +843,7 @@ func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *YAML, filePath st
 	return triggers, nil
 }
 
-func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package, ma whisk.KeyValue) ([]*whisk.Trigger, error) {
+func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package, managedAnnotations whisk.KeyValue) ([]*whisk.Trigger, error) {
 	var errorParser error
 	var listOfTriggers []*whisk.Trigger = make([]*whisk.Trigger, 0)
 
@@ -870,7 +896,7 @@ func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package, ma whisk.Key
 
 		// add managed annotations if its a managed deployment
 		if utils.Flags.Managed || utils.Flags.Sync {
-			wsktrigger.Annotations = append(wsktrigger.Annotations, ma)
+			wsktrigger.Annotations = append(wsktrigger.Annotations, managedAnnotations)
 		}
 
 		listOfTriggers = append(listOfTriggers, wsktrigger)
@@ -878,7 +904,7 @@ func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package, ma whisk.Key
 	return listOfTriggers, nil
 }
 
-func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *YAML, ma whisk.KeyValue) ([]*whisk.Rule, error) {
+func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *YAML, managedAnnotations whisk.KeyValue) ([]*whisk.Rule, error) {
 	var rules []*whisk.Rule = make([]*whisk.Rule, 0)
 	manifestPackages := make(map[string]Package)
 
@@ -889,7 +915,7 @@ func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *YAML, ma whisk.KeyVa
 	}
 
 	for n, p := range manifestPackages {
-		r, err := dm.ComposeRules(p, n, ma)
+		r, err := dm.ComposeRules(p, n, managedAnnotations)
 		if err == nil {
 			rules = append(rules, r...)
 		} else {
@@ -899,7 +925,7 @@ func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *YAML, ma whisk.KeyVa
 	return rules, nil
 }
 
-func (dm *YAMLParser) ComposeRules(pkg Package, packageName string, ma whisk.KeyValue) ([]*whisk.Rule, error) {
+func (dm *YAMLParser) ComposeRules(pkg Package, packageName string, managedAnnotations whisk.KeyValue) ([]*whisk.Rule, error) {
 	var rules []*whisk.Rule = make([]*whisk.Rule, 0)
 
 	for _, rule := range pkg.GetRuleList() {
@@ -923,7 +949,7 @@ func (dm *YAMLParser) ComposeRules(pkg Package, packageName string, ma whisk.Key
 
 		// add managed annotations if its a managed deployment
 		if utils.Flags.Managed || utils.Flags.Sync {
-			wskrule.Annotations = append(wskrule.Annotations, ma)
+			wskrule.Annotations = append(wskrule.Annotations, managedAnnotations)
 		}
 
 		rules = append(rules, wskrule)
@@ -1009,46 +1035,51 @@ func (dm *YAMLParser) ComposeApiRecords(client *whisk.Config, packageName string
 									wski18n.KEY_API:    apiName}))
 					} else {
 						// verify that the action is defined as web action
-						// web-export set to any of [true, yes, raw]
-						if !utils.IsWebAction(pkg.Actions[actionName].Webexport) {
-							return nil, wskderrors.NewYAMLFileFormatError(manifestPath,
-								wski18n.T(wski18n.ID_ERR_API_MISSING_WEB_ACTION_X_action_X_api_X,
-									map[string]interface{}{
-										wski18n.KEY_ACTION: actionName,
-										wski18n.KEY_API:    apiName}))
-						} else {
-							request := new(whisk.ApiCreateRequest)
-							request.ApiDoc = new(whisk.Api)
-							request.ApiDoc.GatewayBasePath = gatewayBasePath
-							// is API verb is valid, it must be one of (GET, PUT, POST, DELETE)
-							request.ApiDoc.GatewayRelPath = gatewayRelPath
-							if _, ok := whisk.ApiVerbs[strings.ToUpper(gatewayMethod)]; !ok {
-								return nil, wskderrors.NewInvalidAPIGatewayMethodError(manifestPath,
-									gatewayBasePath+gatewayRelPath,
-									gatewayMethod,
-									dm.getGatewayMethods())
-							}
-							request.ApiDoc.GatewayMethod = strings.ToUpper(gatewayMethod)
-							request.ApiDoc.Namespace = client.Namespace
-							request.ApiDoc.ApiName = apiName
-							request.ApiDoc.Id = strings.Join([]string{API, request.ApiDoc.Namespace, request.ApiDoc.GatewayRelPath}, ":")
-							// set action of an API Doc
-							request.ApiDoc.Action = new(whisk.ApiAction)
-							if packageName == DEFAULT_PACKAGE {
-								request.ApiDoc.Action.Name = actionName
-							} else {
-								request.ApiDoc.Action.Name = packageName + PATH_SEPARATOR + actionName
+						// web or web-export set to any of [true, yes, raw]
+						a := pkg.Actions[actionName]
+						if !utils.IsWebAction(a.GetWeb()) {
+							warningString := wski18n.T(wski18n.ID_WARN_API_MISSING_WEB_ACTION_X_action_X_api_X,
+								map[string]interface{}{
+									wski18n.KEY_ACTION: actionName,
+									wski18n.KEY_API:    apiName})
+							wskprint.PrintOpenWhiskWarning(warningString)
+							if a.Annotations == nil {
+								a.Annotations = make(map[string]interface{}, 0)
 							}
-							url := []string{HTTPS + client.Host, strings.ToLower(API),
-								API_VERSION, WEB, client.Namespace, packageName,
-								actionName + "." + utils.HTTP_FILE_EXTENSION}
-							request.ApiDoc.Action.Namespace = client.Namespace
-							request.ApiDoc.Action.BackendUrl = strings.Join(url, PATH_SEPARATOR)
-							request.ApiDoc.Action.BackendMethod = gatewayMethod
-							request.ApiDoc.Action.Auth = client.AuthToken
-							// add a newly created ApiCreateRequest object to a list of requests
-							requests = append(requests, request)
+							a.Annotations[utils.WEB_EXPORT_ANNOT] = true
+							pkg.Actions[actionName] = a
+						}
+						request := new(whisk.ApiCreateRequest)
+						request.ApiDoc = new(whisk.Api)
+						request.ApiDoc.GatewayBasePath = gatewayBasePath
+						// is API verb is valid, it must be one of (GET, PUT, POST, DELETE)
+						request.ApiDoc.GatewayRelPath = gatewayRelPath
+						if _, ok := whisk.ApiVerbs[strings.ToUpper(gatewayMethod)]; !ok {
+							return nil, wskderrors.NewInvalidAPIGatewayMethodError(manifestPath,
+								gatewayBasePath+gatewayRelPath,
+								gatewayMethod,
+								dm.getGatewayMethods())
+						}
+						request.ApiDoc.GatewayMethod = strings.ToUpper(gatewayMethod)
+						request.ApiDoc.Namespace = client.Namespace
+						request.ApiDoc.ApiName = apiName
+						request.ApiDoc.Id = strings.Join([]string{API, request.ApiDoc.Namespace, request.ApiDoc.GatewayRelPath}, ":")
+						// set action of an API Doc
+						request.ApiDoc.Action = new(whisk.ApiAction)
+						if packageName == DEFAULT_PACKAGE {
+							request.ApiDoc.Action.Name = actionName
+						} else {
+							request.ApiDoc.Action.Name = packageName + PATH_SEPARATOR + actionName
 						}
+						url := []string{HTTPS + client.Host, strings.ToLower(API),
+							API_VERSION, WEB, client.Namespace, packageName,
+							actionName + "." + utils.HTTP_FILE_EXTENSION}
+						request.ApiDoc.Action.Namespace = client.Namespace
+						request.ApiDoc.Action.BackendUrl = strings.Join(url, PATH_SEPARATOR)
+						request.ApiDoc.Action.BackendMethod = gatewayMethod
+						request.ApiDoc.Action.Auth = client.AuthToken
+						// add a newly created ApiCreateRequest object to a list of requests
+						requests = append(requests, request)
 					}
 				}
 			}
diff --git a/parsers/manifest_parser_test.go b/parsers/manifest_parser_test.go
index 6c64492..c3d62d4 100644
--- a/parsers/manifest_parser_test.go
+++ b/parsers/manifest_parser_test.go
@@ -1033,6 +1033,41 @@ func TestComposeActionsForInvalidWebActions(t *testing.T) {
 	assert.NotNil(t, err, "Expected error for invalid web-export.")
 }
 
+func TestComposeActionsForWebAndWebExport(t *testing.T) {
+	file := "../tests/dat/manifest_data_compose_actions_for_web_and_web_export.yaml"
+	p, m, _ := testLoadParseManifest(t, file)
+
+	actions, err := p.ComposeActionsFromAllPackages(m, m.Filepath, whisk.KeyValue{})
+	assert.Nil(t, err, fmt.Sprintf(TEST_ERROR_COMPOSE_ACTION_FAILURE, file))
+
+	for _, action := range actions {
+		if action.Action.Name == "hello1" || action.Action.Name == "hello2" {
+			for _, a := range action.Action.Annotations {
+				switch a.Key {
+				case "web-export":
+					assert.True(t, a.Value.(bool), "Expected true for web-export but got "+strconv.FormatBool(a.Value.(bool)))
+				}
+			}
+		} else if action.Action.Name == "hello3" {
+			for _, a := range action.Action.Annotations {
+				switch a.Key {
+				case "web-export":
+					assert.False(t, a.Value.(bool), "Expected false for web-export but got "+strconv.FormatBool(a.Value.(bool)))
+				}
+			}
+		} else if action.Action.Name == "hello4" {
+			for _, a := range action.Action.Annotations {
+				switch a.Key {
+				case "web-export":
+					assert.True(t, a.Value.(bool), "Expected true for web-export but got "+strconv.FormatBool(a.Value.(bool)))
+				case "raw-http":
+					assert.True(t, a.Value.(bool), "Expected true for raw but got "+strconv.FormatBool(a.Value.(bool)))
+				}
+			}
+		}
+	}
+}
+
 // Test 16: validate manifest_parser.ResolveParameter() method
 func TestResolveParameterForMultiLineParams(t *testing.T) {
 	paramName := "name"
@@ -1192,10 +1227,11 @@ func TestComposePackage(t *testing.T) {
 
 func TestComposeSequences(t *testing.T) {
 
-	p, m, _ := testLoadParseManifest(t, "../tests/dat/manifest_data_compose_sequences.yaml")
+	file := "../tests/dat/manifest_data_compose_sequences.yaml"
+	p, m, _ := testLoadParseManifest(t, file)
 
 	// Note: set first param (namespace) to empty string
-	seqList, err := p.ComposeSequencesFromAllPackages("", m, whisk.KeyValue{})
+	seqList, err := p.ComposeSequencesFromAllPackages("", m, file, whisk.KeyValue{})
 	if err != nil {
 		assert.Fail(t, "Failed to compose sequences")
 	}
diff --git a/parsers/yamlparser.go b/parsers/yamlparser.go
index c65896f..a4a8b79 100644
--- a/parsers/yamlparser.go
+++ b/parsers/yamlparser.go
@@ -102,9 +102,11 @@ type Action struct {
 	Credential  string                 `yaml:"credential"`
 	ExposedUrl  string                 `yaml:"exposedUrl"`
 	Webexport   string                 `yaml:"web-export"`
+	Web         string                 `yaml:"web"`
 	Main        string                 `yaml:"main"`
 	Docker      string                 `yaml:"docker,omitempty"`
 	Native      bool                   `yaml:"native,omitempty"`
+	Conductor   bool                   `yaml:"conductor,omitempty"`
 	Limits      *Limits                `yaml:"limits"`
 	Inputs      map[string]Parameter   `yaml:"inputs"`
 	Outputs     map[string]Parameter   `yaml:"outputs"`
@@ -125,6 +127,7 @@ type Limits struct {
 
 type Sequence struct {
 	Actions     string                 `yaml:"actions"`
+	Web         string                 `yaml:"web"`
 	Annotations map[string]interface{} `yaml:"annotations,omitempty"`
 }
 
@@ -231,6 +234,15 @@ type YAML struct {
 	Filepath string             //file path of the yaml file
 }
 
+// function to return web-export or web depending on what is specified
+// in manifest and deployment files
+func (action *Action) GetWeb() string {
+	if len(action.Web) == 0 && len(action.Webexport) != 0 {
+		return action.Webexport
+	}
+	return action.Web
+}
+
 // function to return Project or Application depending on what is specified in
 // manifest and deployment files
 func (yaml *YAML) GetProject() Project {
diff --git a/tests/dat/manifest_data_compose_actions_for_web_and_web_export.yaml b/tests/dat/manifest_data_compose_actions_for_web_and_web_export.yaml
new file mode 100644
index 0000000..c6eb509
--- /dev/null
+++ b/tests/dat/manifest_data_compose_actions_for_web_and_web_export.yaml
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+packages:
+  helloworld:
+    actions:
+      hello1:
+        function: ../src/integration/helloworld/actions/hello.js
+        runtime: nodejs
+        web: true
+        web-export: true
+      hello2:
+        function: ../src/integration/helloworld/actions/hello.js
+        runtime: nodejs
+        web: true
+        web-export: false
+      hello3:
+        function: ../src/integration/helloworld/actions/hello.js
+        runtime: nodejs
+        web: false
+        web-export: true
+      hello4:
+        function: ../src/integration/helloworld/actions/hello.js
+        runtime: nodejs
+        web: raw
+        web-export: true
diff --git a/tests/dat/manifest_data_compose_api_records.yaml b/tests/dat/manifest_data_compose_api_records.yaml
index 55cd84e..385c8e9 100644
--- a/tests/dat/manifest_data_compose_api_records.yaml
+++ b/tests/dat/manifest_data_compose_api_records.yaml
@@ -22,19 +22,14 @@ packages:
         web-export: true
       deleteBooks:
         function: ../tests/src/integration/helloworld/actions/hello.js
-        web-export: true
       listMembers:
         function: ../tests/src/integration/helloworld/actions/hello.js
-        web-export: true
       getBooks2:
         function: ../tests/src/integration/helloworld/actions/hello.js
-        web-export: true
       postBooks2:
         function: ../tests/src/integration/helloworld/actions/hello.js
-        web-export: true
       listMembers2:
         function: ../tests/src/integration/helloworld/actions/hello.js
-        web-export: true
     apis:
       book-club:
         club:
diff --git a/tests/src/integration/apigateway/manifest.yml b/tests/src/integration/apigateway/manifest.yml
index 407347a..e2b26f5 100644
--- a/tests/src/integration/apigateway/manifest.yml
+++ b/tests/src/integration/apigateway/manifest.yml
@@ -25,19 +25,14 @@ packages:
                 function: src/greeting.js
                 runtime: nodejs:6
             getBooks:
-                web-export: true
                 function: src/get-books.js
             postBooks:
-                web-export: true
                 function: src/post-books.js
             putBooks:
-                web-export: true
                 function: src/put-books.js
             deleteBooks:
-                web-export: true
                 function: src/delete-books.js
             listMembers:
-                web-export: true
                 function: src/list-members.js
         # new top-level key for defining groups of named APIs
         apis:
diff --git a/tests/src/integration/conductor/actions/increment.js b/tests/src/integration/conductor/actions/increment.js
new file mode 100644
index 0000000..ef3877d
--- /dev/null
+++ b/tests/src/integration/conductor/actions/increment.js
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/*
+ * Increment input value by 1
+ */
+function main({ value }) { return { value: value + 1 } }
diff --git a/tests/src/integration/conductor/actions/triple.js b/tests/src/integration/conductor/actions/triple.js
new file mode 100644
index 0000000..0636d1c
--- /dev/null
+++ b/tests/src/integration/conductor/actions/triple.js
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/*
+ * Return triple of the input value.
+ */
+function main({ value }) { return { value: value * 3 } }
diff --git a/tests/src/integration/conductor/actions/tripleAndIncrement.js b/tests/src/integration/conductor/actions/tripleAndIncrement.js
new file mode 100644
index 0000000..db5bcdb
--- /dev/null
+++ b/tests/src/integration/conductor/actions/tripleAndIncrement.js
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+function main(params) {
+    let step = params.$step || 0
+    delete params.$step
+    package_name = "conductorPackage1"
+    switch (step) {
+        case 0: return { action: package_name+'/triple', params, state: { $step: 1 } }
+        case 1: return { action: package_name+'/increment', params, state: { $step: 2 } }
+        case 2: return { params }
+    }
+}
diff --git a/tests/src/integration/conductor/conductor_test.go b/tests/src/integration/conductor/conductor_test.go
new file mode 100644
index 0000000..162d4f9
--- /dev/null
+++ b/tests/src/integration/conductor/conductor_test.go
@@ -0,0 +1,40 @@
+// +build integration
+
+/*
+ * 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.
+ */
+
+package tests
+
+import (
+	"os"
+	"testing"
+
+	"github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/common"
+	"github.com/stretchr/testify/assert"
+)
+
+const PATH = "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/conductor/"
+
+func TestManagedDeployment(t *testing.T) {
+	manifestPath := os.Getenv("GOPATH") + PATH + "manifest.yaml"
+	wskdeploy := common.NewWskdeploy()
+	_, err := wskdeploy.DeployManifestPathOnly(manifestPath)
+	assert.Equal(t, nil, err, "Failed to deploy based on the manifest file.")
+	_, err = wskdeploy.UndeployManifestPathOnly(manifestPath)
+	assert.Equal(t, nil, err, "Failed to deploy based on the manifest file.")
+
+}
diff --git a/tests/src/integration/conductor/manifest.yaml b/tests/src/integration/conductor/manifest.yaml
new file mode 100644
index 0000000..7e8ce4f
--- /dev/null
+++ b/tests/src/integration/conductor/manifest.yaml
@@ -0,0 +1,52 @@
+#
+# 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.
+#
+project:
+    name: DemoConductor
+    packages:
+        conductorPackage1:
+            actions:
+                triple:
+                    function: actions/triple.js
+                increment:
+                    function: actions/increment.js
+                    runtime: nodejs:6
+                tripleAndIncrement:
+                    function: actions/tripleAndIncrement.js
+                    conductor: true
+        default:
+            actions:
+                triple:
+                    code: |
+                          function main({ value }) { return { value: value * 3 } }
+                    runtime: nodejs:6
+                increment:
+                    code: |
+                          function main({ value }) { return { value: value + 1 } }
+                    runtime: nodejs:6
+                tripleAndIncrement:
+                    code: |
+                          function main(params) {
+                              let step = params.$step || 0
+                              delete params.$step
+                              switch (step) {
+                                  case 0: return { action: 'triple', params, state: { $step: 1 } }
+                                  case 1: return { action: 'increment', params, state: { $step: 2 } }
+                                  case 2: return { params }
+                              }
+                          }
+                    runtime: nodejs:6
+                    conductor: true
+
diff --git a/tests/src/integration/websequence/manifest.yaml b/tests/src/integration/websequence/manifest.yaml
new file mode 100644
index 0000000..ebd9767
--- /dev/null
+++ b/tests/src/integration/websequence/manifest.yaml
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+
+packages:
+    IntegrationTestWebSequence:
+        actions:
+            greeting-1:
+                version: 1.0
+                function: src/greeting.js
+                runtime: nodejs:6
+                inputs:
+                    name: string
+                    place: string
+                outputs:
+                    payload: string
+            greeting-2:
+                version: 1.0
+                function: src/greeting.js
+                runtime: nodejs:6
+                inputs:
+                    name: string
+                    place: string
+                outputs:
+                    payload: string
+        sequences:
+            greeting-sequence-1:
+                actions: greeting-1, greeting-2
+                web: true
+            greeting-sequence-2:
+                actions: greeting-1, greeting-2
+                web: raw
+        triggers:
+            webSequenceTrigger:
+        rules:
+            webSequenceRule:
+                trigger: webSequenceTrigger
+                action: greeting-sequence-1
\ No newline at end of file
diff --git a/tests/src/integration/websequence/src/greeting.js b/tests/src/integration/websequence/src/greeting.js
new file mode 100644
index 0000000..5c01d98
--- /dev/null
+++ b/tests/src/integration/websequence/src/greeting.js
@@ -0,0 +1,32 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/**
+ * Return a simple greeting message for someone.
+ *
+ * @param name A person's name.
+ * @param place Where the person is from.
+ */
+function main(params) {
+    var name = params.name || params.payload || 'stranger';
+    var place = params.place || 'somewhere';
+    return {payload:  'Hello, ' + name + ' from ' + place + '!'};
+}
diff --git a/tests/src/integration/websequence/websequence_test.go b/tests/src/integration/websequence/websequence_test.go
new file mode 100644
index 0000000..7f6eaef
--- /dev/null
+++ b/tests/src/integration/websequence/websequence_test.go
@@ -0,0 +1,43 @@
+// +build integration
+
+/*
+ * 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.
+ */
+
+package tests
+
+import (
+	"os"
+	"testing"
+
+	"github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/common"
+	"github.com/stretchr/testify/assert"
+)
+
+var wskprops = common.GetWskprops()
+
+func TestWebSequence(t *testing.T) {
+	wskdeploy := common.NewWskdeploy()
+	_, err := wskdeploy.Deploy(manifestPath, deploymentPath)
+	assert.Equal(t, nil, err, "Failed to deploy based on the manifest file.")
+	_, err = wskdeploy.Undeploy(manifestPath, deploymentPath)
+	assert.Equal(t, nil, err, "Failed to undeploy based on the manifest file.")
+}
+
+var (
+	manifestPath   = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/websequence/manifest.yaml"
+	deploymentPath = ""
+)
diff --git a/utils/conductor.go b/utils/conductor.go
new file mode 100644
index 0000000..8688d81
--- /dev/null
+++ b/utils/conductor.go
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package utils
+
+import "github.com/apache/incubator-openwhisk-client-go/whisk"
+
+const CONDUCTOR_ANNOTATION = "conductor"
+
+func ConductorAction() whisk.KeyValue {
+	return whisk.KeyValue{
+		Key:   CONDUCTOR_ANNOTATION,
+		Value: true,
+	}
+}
diff --git a/utils/conductor_test.go b/utils/conductor_test.go
new file mode 100644
index 0000000..ffaab6b
--- /dev/null
+++ b/utils/conductor_test.go
@@ -0,0 +1,37 @@
+// +build unit
+
+/*
+ * 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.
+ */
+
+package utils
+
+import (
+	"testing"
+
+	"github.com/apache/incubator-openwhisk-client-go/whisk"
+	"github.com/stretchr/testify/assert"
+)
+
+const MSG_ERR_CONDUCTOR_ACTION_INVALID = "Conductor action does not create conductor annotation"
+
+func TestConductorAction(t *testing.T) {
+	conductor := ConductorAction()
+	annotation := whisk.KeyValue{Key: CONDUCTOR_ANNOTATION, Value: true}
+	assert.NotNil(t, conductor, MSG_ERR_CONDUCTOR_ACTION_INVALID)
+	assert.NotEqual(t, whisk.KeyValue{}, conductor, MSG_ERR_CONDUCTOR_ACTION_INVALID)
+	assert.Equal(t, annotation, conductor, MSG_ERR_CONDUCTOR_ACTION_INVALID)
+}
diff --git a/utils/misc.go b/utils/misc.go
index 3ffe17e..29c7b89 100644
--- a/utils/misc.go
+++ b/utils/misc.go
@@ -19,11 +19,6 @@ package utils
 
 import (
 	"archive/zip"
-	"errors"
-	"fmt"
-	"github.com/apache/incubator-openwhisk-client-go/whisk"
-	"github.com/apache/incubator-openwhisk-wskdeploy/wski18n"
-	"github.com/hokaccha/go-prettyjson"
 	"io"
 	"io/ioutil"
 	"net/http"
@@ -31,8 +26,10 @@ import (
 	"os/user"
 	"path"
 	"path/filepath"
-	"reflect"
 	"strings"
+
+	"github.com/apache/incubator-openwhisk-client-go/whisk"
+	"github.com/hokaccha/go-prettyjson"
 )
 
 const (
@@ -100,15 +97,6 @@ func PrettyJSON(j interface{}) (string, error) {
 	return string(bytes), nil
 }
 
-var kindToJSON []string = []string{"", "boolean", "integer", "integer", "integer", "integer", "integer", "integer", "integer", "integer",
-	"integer", "integer", "integer", "number", "number", "number", "number", "array", "", "", "", "object", "", "", "string", "", ""}
-
-// Gets JSON type name
-func GetJSONType(j interface{}) string {
-	fmt.Print(reflect.TypeOf(j).Kind())
-	return kindToJSON[reflect.TypeOf(j).Kind()]
-}
-
 func NewZipWritter(src, des string) *ZipWritter {
 	zw := &ZipWritter{src: src, des: des}
 	return zw
@@ -165,28 +153,6 @@ func (zw *ZipWritter) Zip() error {
 	return nil
 }
 
-func zipKindError() error {
-	errMsg := wski18n.T("creating an action from a .zip artifact requires specifying the action kind explicitly")
-
-	return errors.New(errMsg)
-}
-
-func extensionError(extension string) error {
-	errMsg := wski18n.T(
-		"'{{.name}}' is not a supported action runtime",
-		map[string]interface{}{
-			"name": extension,
-		})
-
-	return errors.New(errMsg)
-}
-
-func javaEntryError() error {
-	errMsg := wski18n.T("Java actions require --main to specify the fully-qualified name of the main class")
-
-	return errors.New(errMsg)
-}
-
 func deleteKey(key string, keyValueArr whisk.KeyValueArr) whisk.KeyValueArr {
 	for i := 0; i < len(keyValueArr); i++ {
 		if keyValueArr[i].Key == key {
@@ -259,7 +225,7 @@ func (localReader *LocalReader) ReadLocal(path string) ([]byte, error) {
 }
 
 func Read(url string) ([]byte, error) {
-	if strings.HasPrefix(url, "http") {
+	if strings.HasPrefix(url, HTTP_FILE_EXTENSION) {
 		return new(ContentReader).URLReader.ReadUrl(url)
 	} else {
 		return new(ContentReader).LocalReader.ReadLocal(url)
diff --git a/utils/webaction.go b/utils/webaction.go
index 0d6c9c6..087a773 100644
--- a/utils/webaction.go
+++ b/utils/webaction.go
@@ -24,9 +24,12 @@ import (
 )
 
 //for web action support, code from wsk cli with tiny adjustments
-const WEB_EXPORT_ANNOT = "web-export"
-const RAW_HTTP_ANNOT = "raw-http"
-const FINAL_ANNOT = "final"
+const (
+	WEB_EXPORT_ANNOT = "web-export"
+	RAW_HTTP_ANNOT   = "raw-http"
+	FINAL_ANNOT      = "final"
+	TRUE             = "true"
+)
 
 var webExport map[string]string = map[string]string{
 	"TRUE":  "true",
diff --git a/wski18n/i18n_ids.go b/wski18n/i18n_ids.go
index 3677f90..1ede72a 100644
--- a/wski18n/i18n_ids.go
+++ b/wski18n/i18n_ids.go
@@ -175,7 +175,6 @@ const (
 	ID_ERR_RUNTIME_ACTION_SOURCE_NOT_SUPPORTED_X_ext_X_action_X      = "msg_err_runtime_action_source_not_supported"
 	ID_ERR_URL_INVALID_X_urltype_X_url_X_filetype_X                  = "msg_err_url_invalid"
 	ID_ERR_URL_MALFORMED_X_urltype_X_url_X                           = "msg_err_url_malformed"
-	ID_ERR_API_MISSING_WEB_ACTION_X_action_X_api_X                   = "msg_err_api_missing_web_action"
 	ID_ERR_API_MISSING_ACTION_X_action_X_api_X                       = "msg_err_api_missing_action"
 	ID_ERR_ACTION_INVALID_X_action_X                                 = "msg_err_action_invalid"
 	ID_ERR_ACTION_MISSING_RUNTIME_WITH_CODE_X_action_X               = "msg_err_action_missing_runtime_with_code"
@@ -206,6 +205,8 @@ const (
 	ID_WARN_DEPLOYMENT_NAME_NOT_FOUND_X_key_X_name_X          = "msg_warn_deployment_name_not_found"
 	ID_WARN_PROJECT_NAME_OVERRIDDEN                           = "msg_warn_project_name_overridden"
 	ID_WARN_PACKAGE_IS_PUBLIC_X_package_X                     = "msg_warn_package_is_public"
+	ID_WARN_ACTION_WEB_X_action_X                             = "msg_warn_action_web_export_ignored"
+	ID_WARN_API_MISSING_WEB_ACTION_X_action_X_api_X           = "msg_warn_api_missing_web_action"
 
 	// Verbose (Debug/Trace) messages
 	ID_DEBUG_PROJECT_SEARCH_X_path_X_key_X                = "msg_dbg_searching_project_directory"
diff --git a/wski18n/i18n_resources.go b/wski18n/i18n_resources.go
index 2f6894b..7bc2343 100644
--- a/wski18n/i18n_resources.go
+++ b/wski18n/i18n_resources.go
@@ -97,7 +97,7 @@ func wski18nResourcesDe_deAllJson() (*asset, error) {
 	return a, nil
 }
 
-var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\x6d\x8f\xdb\x36\xf2\x7f\x9f\x4f\x31\x08\xfe\x40\x5a\xc0\x51\xd2\xfe\x71\xc0\x21\x40\x5e\xe4\x9a\xb4\xdd\x6b\x93\x0d\x76\xb3\x17\x14\xb9\x85\x42\x4b\x63\x9b\xb5\x44\x0a\x24\x65\xc7\x35\xfc\xdd\x0f\x33\xa4\x1e\xec\x5d\x4a\x5a\xa7\xbd\xeb\x9b\xf3\x2d\x87\x33\xbf\x79\xe0\x70\x66\xa8\x7c\x7a\x04\xb0\x7f\x04\x00\xf0\x58\xe6\x8f\x5f\xc0\xe3\xd2\x2e\xd3\xca\xe0\x42\x7e\x49\xd1\x18\x6d\x1e\xcf\xfc\xaa\x33 [...]
+var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\x6d\x8f\xdb\x36\xf2\x7f\x9f\x4f\x31\x08\xfe\x40\x5a\xc0\x51\xd2\xfe\x71\xc0\x21\xc0\xbe\xc8\x75\xd3\x76\xaf\x4d\x36\xd8\xcd\x5e\x50\xe4\x16\x0a\x2d\x8d\x6d\xd6\x12\x29\x90\x94\x1d\x77\xe1\xef\x7e\x98\x21\xf5\x60\xef\x52\xd2\x3a\xed\x5d\xdf\xd4\x09\x87\x33\xbf\x19\x0e\xe7\x89\xca\xa7\x27\x00\x77\x4f\x00\x00\x9e\xca\xfc\xe9\x2b\x78\x5a\xda\x65\x5a\x19\x5c\xc8\x2f\x29\x1a\xa3\xcd\xd3\x99\x5f\x75\x46 [...]
 
 func wski18nResourcesEn_usAllJsonBytes() ([]byte, error) {
 	return bindataRead(
@@ -112,7 +112,7 @@ func wski18nResourcesEn_usAllJson() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 13874, mode: os.FileMode(420), modTime: time.Unix(1522158494, 0)}
+	info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 14019, mode: os.FileMode(420), modTime: time.Unix(1522283010, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
diff --git a/wski18n/resources/en_US.all.json b/wski18n/resources/en_US.all.json
index 9824ddb..b03692a 100644
--- a/wski18n/resources/en_US.all.json
+++ b/wski18n/resources/en_US.all.json
@@ -292,10 +292,6 @@
     "translation": "JSON input data Missing 'cmd' key"
   },
   {
-    "id": "msg_err_api_missing_web_action",
-    "translation": "Action [{{.action}}] is not a web action, API [{{.api}}] can only be created using web action. Please update manifest file to convert [{{.action}}] to web action.\n"
-  },
-  {
     "id": "msg_err_api_missing_action",
     "translation": "Action [{{.action}}] is missing from manifest file, API [{{.api}}] can only be created based on the action from manifest file. Please update manifest file to include [{{.action}}] as a web action.\n"
   },
@@ -400,6 +396,14 @@
     "translation": "[{{.package}}] is marked as public in manifest file, it will be visible and can be accessed by anyone"
   },
   {
+    "id": "msg_warn_action_web_export_ignored",
+    "translation": "[{{.action}}] has both web and web-export specified, only reading web, ignoring web-export."
+  },
+  {
+    "id": "msg_warn_api_missing_web_action",
+    "translation": "Action [{{.action}}] is not a web action, API [{{.api}}] can only be created using web action. Converting [{{.action}}] to a web action.\n"
+  },
+  {
     "id": "DEBUG",
     "translation": "================= DEBUG ==================="
   },

-- 
To stop receiving notification emails like this one, please contact
mrutkowski@apache.org.