You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by da...@apache.org on 2017/09/20 03:22:25 UTC

[incubator-openwhisk-wskdeploy] branch master updated: Adding support for packages in manifest (#519)

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

daisyguo 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 e6987dd  Adding support for packages in manifest (#519)
e6987dd is described below

commit e6987dddc5fc61c76f9eac187bac8b6d1bf037fe
Author: Priti Desai <pd...@us.ibm.com>
AuthorDate: Tue Sep 19 20:22:23 2017 -0700

    Adding support for packages in manifest (#519)
    
    * Adding support for packages in manifest file
    
    * Adding deployment file
    
    * Adding more changes
    
    * Adding integration test
---
 deployers/manifestreader.go                        | 160 +++++++-------
 deployers/servicedeployer.go                       |   1 -
 parsers/manifest_parser.go                         | 233 +++++++++++++++------
 parsers/manifest_parser_test.go                    | 102 +++++++--
 parsers/yamlparser.go                              |   1 +
 .../actions/hello.jar                              | Bin 0 -> 1053 bytes
 .../validate-packages-in-manifest/actions/hello.js |  26 +++
 .../validate-packages-in-manifest/actions/hello.py |  23 ++
 .../validate-packages-in-manifest/deployment.yaml  |  26 +++
 .../validate-packages-in-manifest/manifest.yaml    | 112 ++++++++++
 .../validate-packages-in-manifest_test.go          |  40 ++++
 11 files changed, 562 insertions(+), 162 deletions(-)

diff --git a/deployers/manifestreader.go b/deployers/manifestreader.go
index 4f409e6..0a8ec28 100644
--- a/deployers/manifestreader.go
+++ b/deployers/manifestreader.go
@@ -46,18 +46,18 @@ func (deployer *ManifestReader) ParseManifest() (*parsers.ManifestYAML, *parsers
 	manifestParser := parsers.NewYAMLParser()
 	manifest, err := manifestParser.ParseManifest(dep.ManifestPath)
 
-    if err != nil {
-        return manifest, manifestParser, utils.NewInputYamlFileError(err.Error())
-    }
+	if err != nil {
+		return manifest, manifestParser, utils.NewInputYamlFileError(err.Error())
+	}
 	return manifest, manifestParser, nil
 }
 
 func (reader *ManifestReader) InitRootPackage(manifestParser *parsers.YAMLParser, manifest *parsers.ManifestYAML) error {
-	packg, err := manifestParser.ComposePackage(manifest, reader.serviceDeployer.ManifestPath)
+	packages, err := manifestParser.ComposeAllPackages(manifest, reader.serviceDeployer.ManifestPath)
 	if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
-	reader.SetPackage(packg)
+		return utils.NewInputYamlFormatError(err.Error())
+	}
+	reader.SetPackage(packages)
 
 	return nil
 }
@@ -65,66 +65,66 @@ func (reader *ManifestReader) InitRootPackage(manifestParser *parsers.YAMLParser
 // Wrapper parser to handle yaml dir
 func (deployer *ManifestReader) HandleYaml(sdeployer *ServiceDeployer, manifestParser *parsers.YAMLParser, manifest *parsers.ManifestYAML) error {
 
-    var err error
+	var err error
 	deps, err := manifestParser.ComposeDependencies(manifest, deployer.serviceDeployer.ProjectPath, deployer.serviceDeployer.ManifestPath)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
-	actions, err := manifestParser.ComposeActions(manifest, deployer.serviceDeployer.ManifestPath)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	actions, err := manifestParser.ComposeActionsFromAllPackages(manifest, deployer.serviceDeployer.ManifestPath)
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
-	sequences, err := manifestParser.ComposeSequences(deployer.serviceDeployer.ClientConfig.Namespace, manifest)
+	sequences, err := manifestParser.ComposeSequencesFromAllPackages(deployer.serviceDeployer.ClientConfig.Namespace, manifest)
 	if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
-	triggers, err := manifestParser.ComposeTriggers(manifest, deployer.serviceDeployer.ManifestPath)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	triggers, err := manifestParser.ComposeTriggersFromAllPackages(manifest, deployer.serviceDeployer.ManifestPath)
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
-	rules, err := manifestParser.ComposeRules(manifest)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	rules, err := manifestParser.ComposeRulesFromAllPackages(manifest)
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
-	apis, err := manifestParser.ComposeApiRecords(manifest)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	apis, err := manifestParser.ComposeApiRecordsFromAllPackages(manifest)
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	err = deployer.SetDependencies(deps)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	err = deployer.SetActions(actions)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	err = deployer.SetSequences(sequences)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	err = deployer.SetTriggers(triggers)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	err = deployer.SetRules(rules)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	err = deployer.SetApis(apis)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	return nil
 
@@ -137,9 +137,9 @@ func (reader *ManifestReader) SetDependencies(deps map[string]utils.DependencyRe
 				// dependency
 				gitReader := utils.NewGitReader(depName, dep)
 				err := gitReader.CloneDependency()
-                if err != nil {
-                    return utils.NewInputYamlFormatError(err.Error())
-                }
+				if err != nil {
+					return utils.NewInputYamlFormatError(err.Error())
+				}
 			} else {
 				// TODO: we should do a check to make sure this dependency is compatible with an already installed one.
 				// If not, we should throw dependency mismatch error.
@@ -155,32 +155,34 @@ func (reader *ManifestReader) SetDependencies(deps map[string]utils.DependencyRe
 	return nil
 }
 
-func (reader *ManifestReader) SetPackage(pkg *whisk.Package) error {
+func (reader *ManifestReader) SetPackage(packages map[string]*whisk.Package) error {
 
 	dep := reader.serviceDeployer
 
 	dep.mt.Lock()
 	defer dep.mt.Unlock()
-	depPkg, exist := dep.Deployment.Packages[pkg.Name]
-	if exist {
-		if dep.IsDefault == true {
-			existPkg := depPkg.Package
-			existPkg.Annotations = pkg.Annotations
-			existPkg.Namespace = pkg.Namespace
-			existPkg.Parameters = pkg.Parameters
-			existPkg.Publish = pkg.Publish
-			existPkg.Version = pkg.Version
-
-			dep.Deployment.Packages[pkg.Name].Package = existPkg
-			return nil
-		} else {
-			return errors.New("Package " + pkg.Name + "exists twice")
+
+	for _, pkg := range packages {
+		depPkg, exist := dep.Deployment.Packages[pkg.Name]
+		if exist {
+			if dep.IsDefault == true {
+				existPkg := depPkg.Package
+				existPkg.Annotations = pkg.Annotations
+				existPkg.Namespace = pkg.Namespace
+				existPkg.Parameters = pkg.Parameters
+				existPkg.Publish = pkg.Publish
+				existPkg.Version = pkg.Version
+
+				dep.Deployment.Packages[pkg.Name].Package = existPkg
+				return nil
+			} else {
+				return errors.New("Package " + pkg.Name + "exists twice")
+			}
 		}
+		newPack := NewDeploymentPackage()
+		newPack.Package = pkg
+		dep.Deployment.Packages[pkg.Name] = newPack
 	}
-
-	newPack := NewDeploymentPackage()
-	newPack.Package = pkg
-	dep.Deployment.Packages[pkg.Name] = newPack
 	return nil
 }
 
@@ -218,9 +220,9 @@ func (reader *ManifestReader) SetActions(actions []utils.ActionRecord) error {
 				}
 
 				err := reader.checkAction(existAction)
-                if err != nil {
-                    return utils.NewInputYamlFormatError(err.Error())
-                }
+				if err != nil {
+					return utils.NewInputYamlFormatError(err.Error())
+				}
 
 			} else {
 				// Action exists, but references two different sources
@@ -229,9 +231,9 @@ func (reader *ManifestReader) SetActions(actions []utils.ActionRecord) error {
 		} else {
 			// not a new action so update the action in the package
 			err := reader.checkAction(manifestAction)
-            if err != nil {
-                return utils.NewInputYamlFormatError(err.Error())
-            }
+			if err != nil {
+				return utils.NewInputYamlFormatError(err.Error())
+			}
 			reader.serviceDeployer.Deployment.Packages[manifestAction.Packagename].Actions[manifestAction.Action.Name] = manifestAction
 		}
 	}
@@ -287,9 +289,9 @@ func (reader *ManifestReader) SetSequences(actions []utils.ActionRecord) error {
 		} else {
 			// not a new action so update the action in the package
 			err := reader.checkAction(seqAction)
-            if err != nil {
-                return utils.NewInputYamlFormatError(err.Error())
-            }
+			if err != nil {
+				return utils.NewInputYamlFormatError(err.Error())
+			}
 			reader.serviceDeployer.Deployment.Packages[seqAction.Packagename].Sequences[seqAction.Action.Name] = seqAction
 		}
 	}
diff --git a/deployers/servicedeployer.go b/deployers/servicedeployer.go
index 32dd4ca..ac57c2d 100644
--- a/deployers/servicedeployer.go
+++ b/deployers/servicedeployer.go
@@ -129,7 +129,6 @@ func (deployer *ServiceDeployer) ConstructDeploymentPlan() error {
         if err != nil {
             return err
         }
-
         fileReader.SetFileActions(fileActions)
     }
 
diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go
index f926aec..720e336 100644
--- a/parsers/manifest_parser.go
+++ b/parsers/manifest_parser.go
@@ -43,8 +43,8 @@ func ReadOrCreateManifest() (*ManifestYAML, error) {
 		dat, _ := ioutil.ReadFile(utils.ManifestFileNameYaml)
 		err := NewYAMLParser().Unmarshal(dat, &maniyaml)
 		if err != nil {
-            return &maniyaml, utils.NewInputYamlFileError(err.Error())
-        }
+			return &maniyaml, utils.NewInputYamlFileError(err.Error())
+		}
 	}
 	return &maniyaml, nil
 }
@@ -52,18 +52,18 @@ func ReadOrCreateManifest() (*ManifestYAML, error) {
 // Serialize manifest to local file
 func Write(manifest *ManifestYAML, filename string) error {
 	output, err := NewYAMLParser().Marshal(manifest)
-    if err != nil {
-        return utils.NewInputYamlFormatError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFormatError(err.Error())
+	}
 
 	f, err := os.Create(filename)
-    if err != nil {
-        return utils.NewInputYamlFileError(err.Error())
-    }
+	if err != nil {
+		return utils.NewInputYamlFileError(err.Error())
+	}
 	defer f.Close()
 
 	f.Write(output)
-    return nil
+	return nil
 }
 
 func (dm *YAMLParser) Unmarshal(input []byte, manifest *ManifestYAML) error {
@@ -89,14 +89,14 @@ func (dm *YAMLParser) ParseManifest(manifestPath string) (*ManifestYAML, error)
 
 	content, err := utils.Read(manifestPath)
 	if err != nil {
-        return &maniyaml, utils.NewInputYamlFileError(err.Error())
-    }
+		return &maniyaml, utils.NewInputYamlFileError(err.Error())
+	}
 
 	err = mm.Unmarshal(content, &maniyaml)
-    if err != nil {
-        lines, msgs := dm.convertErrorToLinesMsgs(err.Error())
-        return &maniyaml, utils.NewParserErr(manifestPath, lines, msgs)
-    }
+	if err != nil {
+		lines, msgs := dm.convertErrorToLinesMsgs(err.Error())
+		return &maniyaml, utils.NewParserErr(manifestPath, lines, msgs)
+	}
 	maniyaml.Filepath = manifestPath
 	return &maniyaml, nil
 }
@@ -164,20 +164,39 @@ func (dm *YAMLParser) ComposeDependencies(mani *ManifestYAML, projectPath string
 	return depMap, nil
 }
 
-// Is we consider multi pacakge in one yaml?
-func (dm *YAMLParser) ComposePackage(mani *ManifestYAML, filePath string) (*whisk.Package, error) {
-	var errorParser error
+func (dm *YAMLParser) ComposeAllPackages(manifest *ManifestYAML, filePath string) (map[string]*whisk.Package, error) {
+	packages := map[string]*whisk.Package{}
+	if manifest.Package.Packagename != "" {
+		s, err := dm.ComposePackage(manifest.Package, manifest.Package.Packagename, filePath)
+		if err == nil {
+			packages[manifest.Package.Packagename] = s
+		} else {
+			return nil, err
+		}
+	} else if manifest.Packages != nil {
+		for n, p := range manifest.Packages {
+			s, err := dm.ComposePackage(p, n, filePath)
+			if err == nil {
+				packages[n] = s
+			} else {
+				return nil, err
+			}
+		}
+	}
+	return packages, nil
+}
 
-	//mani := dm.ParseManifest(manipath)
+func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath string) (*whisk.Package, error) {
+	var errorParser error
 	pag := &whisk.Package{}
-	pag.Name = mani.Package.Packagename
+	pag.Name = packageName
 	//The namespace for this package is absent, so we use default guest here.
-	pag.Namespace = mani.Package.Namespace
+	pag.Namespace = pkg.Namespace
 	pub := false
 	pag.Publish = &pub
 
 	keyValArr := make(whisk.KeyValueArr, 0)
-	for name, param := range mani.Package.Inputs {
+	for name, param := range pkg.Inputs {
 		var keyVal whisk.KeyValue
 		keyVal.Key = name
 
@@ -198,9 +217,26 @@ func (dm *YAMLParser) ComposePackage(mani *ManifestYAML, filePath string) (*whis
 	return pag, nil
 }
 
-func (dm *YAMLParser) ComposeSequences(namespace string, mani *ManifestYAML) ([]utils.ActionRecord, error) {
+func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *ManifestYAML) (ar []utils.ActionRecord, err error) {
 	var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0)
-	for key, sequence := range mani.Package.Sequences {
+	if mani.Package.Packagename != "" {
+		return dm.ComposeSequences(namespace, mani.Package.Sequences, mani.Package.Packagename)
+	} else if mani.Packages != nil {
+		for n, p := range mani.Packages {
+			s, err := dm.ComposeSequences(namespace, p.Sequences, n)
+			if err == nil {
+				s1 = append(s1, s...)
+			} else {
+				return nil, err
+			}
+		}
+	}
+	return s1, nil
+}
+
+func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Sequence, packageName string) ([]utils.ActionRecord, error) {
+	var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0)
+	for key, sequence := range sequences {
 		wskaction := new(whisk.Action)
 		wskaction.Exec = new(whisk.Exec)
 		wskaction.Exec.Kind = "sequence"
@@ -208,11 +244,9 @@ func (dm *YAMLParser) ComposeSequences(namespace string, mani *ManifestYAML) ([]
 
 		var components []string
 		for _, a := range actionList {
-
 			act := strings.TrimSpace(a)
-
-			if !strings.ContainsRune(act, '/') && !strings.HasPrefix(act, mani.Package.Packagename+"/") {
-				act = path.Join(mani.Package.Packagename, act)
+			if !strings.ContainsRune(act, '/') && !strings.HasPrefix(act, packageName +"/") {
+				act = path.Join(packageName, act)
 			}
 			components = append(components, path.Join("/"+namespace, act))
 		}
@@ -236,19 +270,36 @@ func (dm *YAMLParser) ComposeSequences(namespace string, mani *ManifestYAML) ([]
 			wskaction.Annotations = keyValArr
 		}
 
-		record := utils.ActionRecord{wskaction, mani.Package.Packagename, key}
+		record := utils.ActionRecord{Action: wskaction, Packagename: packageName, Filepath: key}
 		s1 = append(s1, record)
 	}
 	return s1, nil
 }
 
-func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []utils.ActionRecord, err error) {
+func (dm *YAMLParser) ComposeActionsFromAllPackages(manifest *ManifestYAML, filePath string) ([]utils.ActionRecord, error) {
+	var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0)
+	if manifest.Package.Packagename != "" {
+		return dm.ComposeActions(filePath, manifest.Package.Actions, manifest.Package.Packagename)
+	} else if manifest.Packages != nil {
+		for n, p := range manifest.Packages {
+			a, err := dm.ComposeActions(filePath, p.Actions, n)
+			if err == nil {
+				s1 = append(s1, a...)
+			} else {
+				return nil, err
+			}
+		}
+	}
+	return s1, nil
+}
+
+func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, packageName string) ([]utils.ActionRecord, error) {
 
 	var errorParser error
 	var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0)
 
-	for key, action := range mani.Package.Actions {
-		splitmanipath := strings.Split(manipath, string(os.PathSeparator))
+	for key, action := range actions {
+		splitFilePath := strings.Split(filePath, string(os.PathSeparator))
 		//set action.Function to action.Location
 		//because Location is deprecated in Action entity
 		if action.Function == "" && action.Location != "" {
@@ -260,11 +311,14 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 
 		wskaction.Exec = new(whisk.Exec)
 		if action.Function != "" {
-			filePath := strings.TrimRight(manipath, splitmanipath[len(splitmanipath)-1]) + action.Function
+			filePath := strings.TrimRight(filePath, splitFilePath[len(splitFilePath)-1]) + action.Function
 
 			if utils.IsDirectory(filePath) {
 				zipName := filePath + ".zip"
-				err = utils.NewZipWritter(filePath, zipName).Zip()
+				err := utils.NewZipWritter(filePath, zipName).Zip()
+				if err != nil {
+					return nil, err
+				}
 				defer os.Remove(zipName)
 				// To do: support docker and main entry as did by go cli?
 				wskaction.Exec, err = utils.GetExec(zipName, action.Runtime, false, "")
@@ -287,8 +341,8 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 					kind = "java"
 				default:
 					kind = "nodejs:6"
-                    errStr := wski18n.T("Unsupported runtime type, set to nodejs")
-                    whisk.Debug(whisk.DbgWarn, errStr)
+					errStr := wski18n.T("Unsupported runtime type, set to nodejs")
+					whisk.Debug(whisk.DbgWarn, errStr)
 					//add the user input kind here
 				}
 
@@ -296,9 +350,9 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 
 				action.Function = filePath
 				dat, err := utils.Read(filePath)
-                if err != nil {
-                    return s1, err
-                }
+				if err != nil {
+					return s1, err
+				}
 				code := string(dat)
 				if ext == ".zip" || ext == ".jar" {
 					code = base64.StdEncoding.EncodeToString([]byte(dat))
@@ -315,13 +369,13 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 			if utils.CheckExistRuntime(action.Runtime, utils.Rts) {
 				wskaction.Exec.Kind = action.Runtime
 			} else {
-                errStr := wski18n.T("the runtime is not supported by Openwhisk platform.\n")
-                whisk.Debug(whisk.DbgWarn, errStr)
+				errStr := wski18n.T("the runtime is not supported by Openwhisk platform.\n")
+				whisk.Debug(whisk.DbgWarn, errStr)
 			}
 		} else {
-            errStr := wski18n.T("wskdeploy has chosen a particular runtime for the action.\n")
-            whisk.Debug(whisk.DbgWarn, errStr)
-        }
+			errStr := wski18n.T("wskdeploy has chosen a particular runtime for the action.\n")
+			whisk.Debug(whisk.DbgWarn, errStr)
+		}
 
 		// we can specify the name of the action entry point using main
 		if action.Main != "" {
@@ -333,7 +387,7 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 			var keyVal whisk.KeyValue
 			keyVal.Key = name
 
-			keyVal.Value, errorParser = ResolveParameter(name, &param, manipath)
+			keyVal.Value, errorParser = ResolveParameter(name, &param, filePath)
 
 			if errorParser != nil {
 				return nil, errorParser
@@ -343,7 +397,6 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 				keyValArr = append(keyValArr, keyVal)
 			}
 		}
-
 		if len(keyValArr) > 0 {
 			wskaction.Parameters = keyValArr
 		}
@@ -360,29 +413,44 @@ func (dm *YAMLParser) ComposeActions(mani *ManifestYAML, manipath string) (ar []
 		// only set the webaction when the annotations are not empty.
 		if action.Webexport == "true" {
 			//wskaction.Annotations = keyValArr
-			wskaction.Annotations, err = utils.WebAction("yes", keyValArr, action.Name, false)
-            if err != nil {
-                return s1, err
-            }
+			wskaction.Annotations, errorParser = utils.WebAction("yes", keyValArr, action.Name, false)
+			if errorParser != nil {
+				return s1, errorParser
+			}
 		}
 
 		wskaction.Name = key
 		pub := false
 		wskaction.Publish = &pub
 
-		record := utils.ActionRecord{wskaction, mani.Package.Packagename, action.Function}
+		record := utils.ActionRecord{Action: wskaction, Packagename: packageName, Filepath: action.Function}
 		s1 = append(s1, record)
-
 	}
 
 	return s1, nil
 
 }
 
-func (dm *YAMLParser) ComposeTriggers(manifest *ManifestYAML, filePath string) ([]*whisk.Trigger, error) {
+func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *ManifestYAML, filePath string) ([]*whisk.Trigger, error) {
+	var triggers []*whisk.Trigger = make([]*whisk.Trigger, 0)
+	if manifest.Package.Packagename != "" {
+		return dm.ComposeTriggers(filePath, manifest.Package)
+	} else if manifest.Packages != nil {
+		for _, p := range manifest.Packages {
+			t, err := dm.ComposeTriggers(filePath, p)
+			if err == nil {
+				triggers = append(triggers, t...)
+			} else {
+				return nil, err
+			}
+		}
+	}
+	return triggers, nil
+}
+
+func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package) ([]*whisk.Trigger, error) {
 	var errorParser error
 	var t1 []*whisk.Trigger = make([]*whisk.Trigger, 0)
-	pkg := manifest.Package
 	for _, trigger := range pkg.GetTriggerList() {
 		wsktrigger := new(whisk.Trigger)
 		wsktrigger.Name = trigger.Name
@@ -436,29 +504,56 @@ func (dm *YAMLParser) ComposeTriggers(manifest *ManifestYAML, filePath string) (
 	return t1, nil
 }
 
-func (dm *YAMLParser) ComposeRules(manifest *ManifestYAML) ([]*whisk.Rule, error) {
+func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *ManifestYAML) ([]*whisk.Rule, error) {
+	var rules []*whisk.Rule = make([]*whisk.Rule, 0)
+	if manifest.Package.Packagename != "" {
+		return dm.ComposeRules(manifest.Package, manifest.Package.Packagename)
+	} else if manifest.Packages != nil {
+		for n, p := range manifest.Packages {
+			r, err := dm.ComposeRules(p, n)
+			if err == nil {
+				rules = append(rules, r...)
+			} else {
+				return nil, err
+			}
+		}
+	}
+	return rules, nil
+}
 
+
+func (dm *YAMLParser) ComposeRules(pkg Package, packageName string) ([]*whisk.Rule, error) {
 	var r1 []*whisk.Rule = make([]*whisk.Rule, 0)
-	pkg := manifest.Package
 	for _, rule := range pkg.GetRuleList() {
 		wskrule := rule.ComposeWskRule()
-
 		act := strings.TrimSpace(wskrule.Action.(string))
-
-		if !strings.ContainsRune(act, '/') && !strings.HasPrefix(act, pkg.Packagename+"/") {
-			act = path.Join(pkg.Packagename, act)
+		if !strings.ContainsRune(act, '/') && !strings.HasPrefix(act, packageName+"/") {
+			act = path.Join(packageName, act)
 		}
-
 		wskrule.Action = act
-
 		r1 = append(r1, wskrule)
 	}
-
 	return r1, nil
 }
 
-func (dm *YAMLParser) ComposeApiRecords(manifest *ManifestYAML) ([]*whisk.ApiCreateRequest, error) {
-	pkg := manifest.Package
+func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(manifest *ManifestYAML) ([]*whisk.ApiCreateRequest, error) {
+	var requests []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0)
+	if manifest.Package.Packagename != "" {
+		return dm.ComposeApiRecords(manifest.Package)
+	} else if manifest.Packages != nil {
+		for _, p := range manifest.Packages {
+			r, err := dm.ComposeApiRecords(p)
+			if err == nil {
+				requests = append(requests, r...)
+			} else {
+				return nil, err
+			}
+		}
+	}
+	return requests, nil
+}
+
+func (dm *YAMLParser) ComposeApiRecords(pkg Package) ([]*whisk.ApiCreateRequest, error) {
 	var acq []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0)
 	apis := pkg.GetApis()
 	for _, api := range apis {
@@ -539,8 +634,8 @@ func ResolveParamTypeFromValue(value interface{}, filePath string) (string, erro
 			// TODO(): We have information to display to user on an error or warning here
 			// TODO(): specifically, we have the parameter name, its value to show on error/warning
 			// TODO(): perhaps this is a different Class of error?  e.g., ErrorParameterMismatchError
-                        lines := []string{"Line Unknown"}
-                        msgs := []string{"Parameter value is not a known type. ["+actualType+"]"}
+			lines := []string{"Line Unknown"}
+			msgs := []string{"Parameter value is not a known type. [" + actualType + "]"}
 			err = utils.NewParserErr(filePath, lines, msgs)
 		}
 	} else {
@@ -598,8 +693,8 @@ func ResolveParameter(paramName string, param *Parameter, filePath string) (inte
 
 		// if we do not have a value or default, but have a type, find its default and use it for the value
 		if param.Type != "" && !isValidParameterType(param.Type) {
-                        lines := []string{"Line Unknown"}
-                        msgs := []string{"Invalid Type for parameter. ["+param.Type+"]"}
+			lines := []string{"Line Unknown"}
+			msgs := []string{"Invalid Type for parameter. [" + param.Type + "]"}
 			return value, utils.NewParserErr(filePath, lines, msgs)
 		} else if param.Type == "" {
 			param.Type = tempType
diff --git a/parsers/manifest_parser_test.go b/parsers/manifest_parser_test.go
index 8858f0f..0dfffcf 100644
--- a/parsers/manifest_parser_test.go
+++ b/parsers/manifest_parser_test.go
@@ -485,7 +485,7 @@ func TestComposeActionsForImplicitRuntimes(t *testing.T) {
             // read and parse manifest.yaml file
             p := NewYAMLParser()
             m, _ := p.ParseManifest(tmpfile.Name())
-            actions, err := p.ComposeActions(m, tmpfile.Name())
+            actions, err := p.ComposeActionsFromAllPackages(m, tmpfile.Name())
             var expectedResult string
             if err == nil {
                 for i := 0; i < len(actions); i++ {
@@ -527,7 +527,7 @@ func TestComposeActionsForInvalidRuntime(t *testing.T) {
             // read and parse manifest.yaml file
             p := NewYAMLParser()
             m, _ := p.ParseManifest(tmpfile.Name())
-            _, err := p.ComposeActions(m, tmpfile.Name())
+            _, err := p.ComposeActionsFromAllPackages(m, tmpfile.Name())
             // (TODO) uncomment the following test case after issue #307 is fixed
             // (TODO) its failing right now as we are lacking check on invalid runtime
             // assert.NotNil(t, err, "Invalid runtime, ComposeActions should report an error")
@@ -546,7 +546,7 @@ func TestComposeActionsForSingleLineParams(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(manifestFile)
-    actions, err := p.ComposeActions(m, manifestFile)
+    actions, err := p.ComposeActionsFromAllPackages(m, manifestFile)
 
     if err == nil {
         // assert that the actions variable has only one action
@@ -631,7 +631,7 @@ func TestComposeActionsForMultiLineParams(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(manifestFile)
-    actions, err := p.ComposeActions(m, manifestFile)
+    actions, err := p.ComposeActionsFromAllPackages(m, manifestFile)
 
     if err == nil {
         // assert that the actions variable has only one action
@@ -711,7 +711,7 @@ func TestComposeActionsForFunction(t *testing.T) {
             // read and parse manifest.yaml file
             p := NewYAMLParser()
             m, _ := p.ParseManifest(tmpfile.Name())
-            actions, err := p.ComposeActions(m, tmpfile.Name())
+            actions, err := p.ComposeActionsFromAllPackages(m, tmpfile.Name())
             var expectedResult, actualResult string
             if err == nil {
                 for i := 0; i < len(actions); i++ {
@@ -796,7 +796,7 @@ func TestComposeActionsForWebActions(t *testing.T) {
             // read and parse manifest.yaml file
             p := NewYAMLParser()
             m, _ := p.ParseManifest(tmpfile.Name())
-            actions, err := p.ComposeActions(m, tmpfile.Name())
+            actions, err := p.ComposeActionsFromAllPackages(m, tmpfile.Name())
             if err == nil {
                 for i := 0; i < len(actions); i++ {
                     if actions[i].Action.Name == "hello" {
@@ -906,10 +906,12 @@ func TestComposePackage(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(tmpfile.Name())
-    pkg, err := p.ComposePackage(m, tmpfile.Name())
+    pkg, err := p.ComposeAllPackages(m, tmpfile.Name())
     if err == nil {
-        assert.Equal(t, "helloworld", pkg.Name, "Failed to get package name")
-        assert.Equal(t, "default", pkg.Namespace, "Failed to get package namespace")
+        n := "helloworld"
+        assert.NotNil(t, pkg[n], "Failed to get the whole package")
+        assert.Equal(t, n, pkg[n].Name, "Failed to get package name")
+        assert.Equal(t, "default", pkg[n].Namespace, "Failed to get package namespace")
     } else {
         assert.Fail(t, "Failed to compose package")
     }
@@ -934,7 +936,7 @@ func TestComposeSequences(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(tmpfile.Name())
-    seqList, err := p.ComposeSequences("", m)
+    seqList, err := p.ComposeSequencesFromAllPackages("", m)
     if err != nil {
         assert.Fail(t, "Failed to compose sequences")
     }
@@ -981,7 +983,7 @@ func TestComposeTriggers(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(tmpfile.Name())
-    triggerList, err := p.ComposeTriggers(m, tmpfile.Name())
+    triggerList, err := p.ComposeTriggersFromAllPackages(m, tmpfile.Name())
     if err != nil {
         assert.Fail(t, "Failed to compose trigger")
     }
@@ -1020,7 +1022,7 @@ func TestComposeRules(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(tmpfile.Name())
-    ruleList, err := p.ComposeRules(m)
+    ruleList, err := p.ComposeRulesFromAllPackages(m)
     if err != nil {
         assert.Fail(t, "Failed to compose rules")
     }
@@ -1066,7 +1068,7 @@ func TestComposeApiRecords(t *testing.T) {
     // read and parse manifest.yaml file
     p := NewYAMLParser()
     m, _ := p.ParseManifest(tmpfile.Name())
-    apiList, err := p.ComposeApiRecords(m)
+    apiList, err := p.ComposeApiRecordsFromAllPackages(m)
     if err != nil {
         assert.Fail(t, "Failed to compose api records")
     }
@@ -1221,4 +1223,78 @@ func TestMissingRootValueManifestYaml(t *testing.T) {
     _, err = p.ParseManifest(tmpfile.Name())
     assert.NotNil(t, err)
     assert.Contains(t, err.Error(), "field actions not found in struct parsers.ManifestYAML: Line 1, its neighbour lines, or the lines on the same level")
+
+}
+
+// validate manifest_parser:Unmarshal() method for package in manifest YAML
+// validate that manifest_parser is able to read and parse the manifest data
+func TestUnmarshalForPackages(t *testing.T) {
+    data := `
+packages:
+  package1:
+    actions:
+      helloNodejs:
+        function: actions/hello.js
+        runtime: nodejs:6
+  package2:
+    actions:
+      helloPython:
+        function: actions/hello.py
+        runtime: python`
+    // set the zero value of struct ManifestYAML
+    m := ManifestYAML{}
+    // Unmarshal reads/parses manifest data and sets the values of ManifestYAML
+    // And returns an error if parsing a manifest data fails
+    err := NewYAMLParser().Unmarshal([]byte(data), &m)
+    if err == nil {
+        expectedResult := string(2)
+        actualResult := string(len(m.Packages))
+        assert.Equal(t, expectedResult, actualResult, "Expected 2 packages but got " + actualResult)
+        // we have two packages
+        // package name should be "helloNodejs" and "helloPython"
+        for k, v := range m.Packages {
+            switch k {
+            case "package1":
+                assert.Equal(t, "package1", k, "Expected package name package1 but got " + k)
+                expectedResult = string(1)
+                actualResult = string(len(v.Actions))
+                assert.Equal(t, expectedResult, actualResult, "Expected 1 but got " + actualResult)
+                // get the action payload from the map of actions which is stored in
+                // ManifestYAML.Package.Actions with the type of map[string]Action
+                actionName := "helloNodejs"
+                if action, ok := v.Actions[actionName]; ok {
+                    // location/function of an action should be "actions/hello.js"
+                    expectedResult = "actions/hello.js"
+                    actualResult = action.Function
+                    assert.Equal(t, expectedResult, actualResult, "Expected action function " + expectedResult + " but got " + actualResult)
+                    // runtime of an action should be "nodejs:6"
+                    expectedResult = "nodejs:6"
+                    actualResult = action.Runtime
+                    assert.Equal(t, expectedResult, actualResult, "Expected action runtime " + expectedResult + " but got " + actualResult)
+                } else {
+                    t.Error("Action named " + actionName + " does not exist.")
+                }
+            case "package2":
+                assert.Equal(t, "package2", k, "Expected package name package2 but got " + k)
+                expectedResult = string(1)
+                actualResult = string(len(v.Actions))
+                assert.Equal(t, expectedResult, actualResult, "Expected 1 but got " + actualResult)
+                // get the action payload from the map of actions which is stored in
+                // ManifestYAML.Package.Actions with the type of map[string]Action
+                actionName := "helloPython"
+                if action, ok := v.Actions[actionName]; ok {
+                    // location/function of an action should be "actions/hello.js"
+                    expectedResult = "actions/hello.py"
+                    actualResult = action.Function
+                    assert.Equal(t, expectedResult, actualResult, "Expected action function " + expectedResult + " but got " + actualResult)
+                    // runtime of an action should be "python"
+                    expectedResult = "python"
+                    actualResult = action.Runtime
+                    assert.Equal(t, expectedResult, actualResult, "Expected action runtime " + expectedResult + " but got " + actualResult)
+                } else {
+                    t.Error("Action named " + actionName + " does not exist.")
+                }
+            }
+        }
+    }
 }
diff --git a/parsers/yamlparser.go b/parsers/yamlparser.go
index 2005801..a856396 100644
--- a/parsers/yamlparser.go
+++ b/parsers/yamlparser.go
@@ -173,6 +173,7 @@ type DeploymentYAML struct {
 
 type ManifestYAML struct {
 	Package  Package `yaml:"package"` //used in both manifest.yaml and deployment.yaml
+	Packages map[string]Package `yaml:"packages"`
 	Filepath string  //file path of the yaml file
 }
 
diff --git a/tests/src/integration/validate-packages-in-manifest/actions/hello.jar b/tests/src/integration/validate-packages-in-manifest/actions/hello.jar
new file mode 100644
index 0000000..4b4e55c
Binary files /dev/null and b/tests/src/integration/validate-packages-in-manifest/actions/hello.jar differ
diff --git a/tests/src/integration/validate-packages-in-manifest/actions/hello.js b/tests/src/integration/validate-packages-in-manifest/actions/hello.js
new file mode 100644
index 0000000..25fdafb
--- /dev/null
+++ b/tests/src/integration/validate-packages-in-manifest/actions/hello.js
@@ -0,0 +1,26 @@
+/*
+ * 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 the whole world.
+ */
+function main(params) {
+    msg = "Hello, " + params.name + " from " + params.place;
+    console.log(msg)
+    return { payload:  msg };
+}
+
diff --git a/tests/src/integration/validate-packages-in-manifest/actions/hello.py b/tests/src/integration/validate-packages-in-manifest/actions/hello.py
new file mode 100644
index 0000000..44c3bd1
--- /dev/null
+++ b/tests/src/integration/validate-packages-in-manifest/actions/hello.py
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+def main(args):
+    name = args.get("name", "stranger")
+    greeting = "Hello " + name + "!"
+    print(greeting)
+    return {"greeting": greeting}
+
diff --git a/tests/src/integration/validate-packages-in-manifest/deployment.yaml b/tests/src/integration/validate-packages-in-manifest/deployment.yaml
new file mode 100644
index 0000000..5ee0f75
--- /dev/null
+++ b/tests/src/integration/validate-packages-in-manifest/deployment.yaml
@@ -0,0 +1,26 @@
+application:
+  name: IntegrationTest
+  packages:
+    packageNodeJS:
+      actions:
+        helloNodejs-1:
+          inputs:
+            name: Amy
+            place: Paris
+        helloNodejs-3:
+          inputs:
+            name: Arthur
+            place: Hawaii
+    packagePython:
+      actions:
+        helloPython-1:
+          inputs:
+            name: Henry
+        helloPython-2:
+          inputs:
+            name: Alex
+    packageJava:
+      actions:
+        helloJava-1:
+          inputs:
+            name: Bob
diff --git a/tests/src/integration/validate-packages-in-manifest/manifest.yaml b/tests/src/integration/validate-packages-in-manifest/manifest.yaml
new file mode 100644
index 0000000..81679f6
--- /dev/null
+++ b/tests/src/integration/validate-packages-in-manifest/manifest.yaml
@@ -0,0 +1,112 @@
+packages:
+    packageNodeJS:
+        actions:
+            helloNodejs-1:
+                function: actions/hello.js
+                runtime: nodejs:6
+                inputs:
+                    name:
+                        type: string
+                        description: name of a person
+                    place:
+                        type: string
+                        description: location of a person
+                outputs:
+                    payload:
+                        type: string
+                        description: a simple greeting message, Hello World!
+            helloNodejs-2:
+                function: actions/hello.js
+                runtime: nodejs:6
+            helloNodejs-3:
+                function: actions/hello.js
+                runtime: nodejs:6
+                inputs:
+                    name:
+                        type: string
+                        description: name of a person
+                    place:
+                        type: string
+                        description: location of a person
+        sequences:
+            helloworldnodejs-series:
+                actions: helloNodejs-1, helloNodejs-2, helloNodejs-3
+        triggers:
+            triggerNodeJS:
+        rules:
+            ruleNodeJS:
+                trigger: triggerNodeJS
+                action: helloworldnodejs-series
+    packagePython:
+        actions:
+            helloPython-1:
+                function: actions/hello.py
+                runtime: python
+                inputs:
+                    name:
+                        type: string
+                        description: name of a person
+                outputs:
+                    payload:
+                        type: string
+                        description: a simple greeting message, Hello Henry!
+            helloPython-2:
+                function: actions/hello.py
+                runtime: python
+                inputs:
+                    name:
+                        type: string
+                        description: name of a person
+                outputs:
+                    payload:
+                        type: string
+                        description: a simple greeting message, Hello Alex!
+            helloPython-3:
+                function: actions/hello.py
+                runtime: python
+        sequences:
+            helloworldpython-series:
+                actions: helloPython-1, helloPython-2, helloPython-3
+        triggers:
+            triggerPython:
+        rules:
+            rulePython:
+                trigger: triggerPython
+                action: helloworldpython-series
+    packageJava:
+        actions:
+            helloJava-1:
+                function: actions/hello.jar
+                runtime: java
+                main: Hello
+                inputs:
+                    name:
+                        type: string
+                        description: name of a person
+                outputs:
+                    payload:
+                        type: string
+                        description: a simple greeting message, Hello Bob!
+            helloJava-2:
+                function: actions/hello.jar
+                runtime: java
+                main: Hello
+            helloJava-3:
+                function: actions/hello.jar
+                runtime: java
+                main: Hello
+        triggers:
+            triggerJava:
+        rules:
+            ruleJava:
+                trigger: triggerJava
+                action: helloJava-1
+    packageBuggy:
+        actions:
+            helloJava-1:
+                function: actions/hello.jar
+                runtime: java
+                main: Hello
+        triggers:
+            triggerJava:
+
diff --git a/tests/src/integration/validate-packages-in-manifest/validate-packages-in-manifest_test.go b/tests/src/integration/validate-packages-in-manifest/validate-packages-in-manifest_test.go
new file mode 100644
index 0000000..08c2e56
--- /dev/null
+++ b/tests/src/integration/validate-packages-in-manifest/validate-packages-in-manifest_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 (
+	"github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/common"
+	"github.com/stretchr/testify/assert"
+	"os"
+	"testing"
+)
+
+func TestPackagesInManifest(t *testing.T) {
+	wskdeploy := common.NewWskdeploy()
+	_, err := wskdeploy.Deploy(manifestPath, deploymentPath)
+	assert.Equal(t, nil, err, "Failed to deploy based on the manifest and deployment files.")
+	_, err = wskdeploy.Undeploy(manifestPath, deploymentPath)
+	assert.Equal(t, nil, err, "Failed to undeploy based on the manifest and deployment files.")
+}
+
+var (
+	manifestPath   = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/validate-packages-in-manifest/manifest.yaml"
+	deploymentPath = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/validate-packages-in-manifest/deployment.yaml"
+)

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].