You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/03/15 11:17:45 UTC

[GitHub] kpavel closed pull request #802: WIP: Resolves #730

kpavel closed pull request #802: WIP: Resolves #730
URL: https://github.com/apache/incubator-openwhisk-wskdeploy/pull/802
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/cmd/export.go b/cmd/export.go
index ede3ba02..05d400d7 100644
--- a/cmd/export.go
+++ b/cmd/export.go
@@ -18,7 +18,6 @@
 package cmd
 
 import (
-	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -123,6 +122,34 @@ func ExportAction(actionName string, packageName string, maniyaml *parsers.YAML)
 	return nil
 }
 
+func ContainsManageProjectAnnotation(annotations whisk.KeyValueArr, projectName string) (res bool) {
+	// get the list of managed projects
+	if annotation := annotations.GetValue(utils.MANAGED_LIST); annotation != nil {
+		// decode the JSON blob and retrieve __OW_PROJECT_NAME_LIST
+		managedList := annotation.([]interface{})
+
+		for _, a := range managedList {
+			managed := a.(map[string]interface{})
+			if managed[utils.OW_PROJECT_NAME] == projectName {
+				return true
+			}
+		}
+	}
+
+	// for backward compatibility check 'managed' annotation as well
+	if a := annotations.GetValue(utils.MANAGED); a != nil {
+		// decode the JSON blob and retrieve __OW_PROJECT_NAME
+		pa := a.(map[string]interface{})
+
+		// we have found a package which is part of the current project
+		if pa[utils.OW_PROJECT_NAME] == projectName {
+			return true
+		}
+	}
+
+	return false
+}
+
 func ExportCmdImp(cmd *cobra.Command, args []string) error {
 
 	projectName := utils.Flags.ProjectPath
@@ -146,52 +173,38 @@ func ExportCmdImp(cmd *cobra.Command, args []string) error {
 	// add to export when managed project name matches with the
 	// specified project name
 	for _, pkg := range packages {
-		if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil {
-			// decode the JSON blob and retrieve __OW_PROJECT_NAME
-			pa := a.(map[string]interface{})
-
-			// we have found a package which is part of the current project
-			if pa[utils.OW_PROJECT_NAME] == projectName {
-
-				if maniyaml.Packages == nil {
-					maniyaml.Packages = make(map[string]parsers.Package)
-				}
+		if ContainsManageProjectAnnotation(pkg.Annotations, projectName) {
+			if maniyaml.Packages == nil {
+				maniyaml.Packages = make(map[string]parsers.Package)
+			}
 
-				maniyaml.Packages[pkg.Name] = *maniyaml.ComposeParsersPackage(pkg)
-				// TODO: throw if there more than single package managed by project
-				// currently will be a mess because triggers and rules managed under packages
-				// instead of the top level (similar to OW model)
-				if len(maniyaml.Packages) > 1 {
-					return errors.New("currently can't work with more than one package managed by one project")
-				}
+			maniyaml.Packages[pkg.Name] = *maniyaml.ComposeParsersPackage(pkg)
 
-				// perform the similar check on the list of actions from this package
-				// get a list of actions in your namespace
-				actions, _, err := client.Actions.List(pkg.Name, &whisk.ActionListOptions{})
-				if err != nil {
-					return err
-				}
+			// perform the similar check on the list of actions from this package
+			// get a list of actions in your namespace
+			actions, _, err := client.Actions.List(pkg.Name, &whisk.ActionListOptions{})
+			if err != nil {
+				return err
+			}
 
-				// iterate over list of managed package actions to find an action with managed annotations
-				// check if "managed" annotation is attached to an action
-				for _, action := range actions {
-					// TODO: consider to throw error when there unmanaged or "foreign" assets under managed package
-					// an annotation with "managed" key indicates that an action was deployed as part of managed deployment
-					// if such annotation exists, check if it belongs to the current managed deployment
-					// this action has attached managed annotations
-					if a := action.Annotations.GetValue(utils.MANAGED); a != nil {
-						aa := a.(map[string]interface{})
-						if aa[utils.OW_PROJECT_NAME] == projectName {
-							actionName := strings.Join([]string{pkg.Name, action.Name}, "/")
-							// export action to file system
-							err = ExportAction(actionName, pkg.Name, maniyaml)
-							if err != nil {
-								return err
-							}
-						}
+			// iterate over list of managed package actions to find an action with managed annotations
+			// check if "managed" annotation is attached to an action
+			for _, action := range actions {
+				// TODO: consider to throw error when there unmanaged or "foreign" assets under managed package
+				// an annotation with "managed" key indicates that an action was deployed as part of managed deployment
+				// if such annotation exists, check if it belongs to the current managed deployment
+				// this action has attached managed annotations
+				if ContainsManageProjectAnnotation(action.Annotations, projectName) {
+					actionName := strings.Join([]string{pkg.Name, action.Name}, "/")
+					// export action to file system
+					err = ExportAction(actionName, pkg.Name, maniyaml)
+					if err != nil {
+						return err
 					}
+
 				}
 			}
+
 		}
 	}
 
@@ -204,23 +217,17 @@ func ExportCmdImp(cmd *cobra.Command, args []string) error {
 	// iterate over the list of triggers to determine whether any of them part of specified managed project
 	for _, trg := range triggers {
 		// trigger has attached managed annotation
-		if a := trg.Annotations.GetValue(utils.MANAGED); a != nil {
-			// decode the JSON blob and retrieve __OW_PROJECT_NAME
-			ta := a.(map[string]interface{})
-			if ta[utils.OW_PROJECT_NAME] == projectName {
-
-				//				for i := 0; i < len(maniyaml.Packages); i++ {
-				for pkgName := range maniyaml.Packages {
-					if maniyaml.Packages[pkgName].Namespace == trg.Namespace {
-						if maniyaml.Packages[pkgName].Triggers == nil {
-							pkg := maniyaml.Packages[pkgName]
-							pkg.Triggers = make(map[string]parsers.Trigger)
-							maniyaml.Packages[pkgName] = pkg
-						}
-
-						// export trigger to manifest
-						maniyaml.Packages[pkgName].Triggers[trg.Name] = *maniyaml.ComposeParsersTrigger(trg)
+		if ContainsManageProjectAnnotation(trg.Annotations, projectName) {
+			for pkgName := range maniyaml.Packages {
+				if maniyaml.Packages[pkgName].Namespace == trg.Namespace {
+					if maniyaml.Packages[pkgName].Triggers == nil {
+						pkg := maniyaml.Packages[pkgName]
+						pkg.Triggers = make(map[string]parsers.Trigger)
+						maniyaml.Packages[pkgName] = pkg
 					}
+
+					// export trigger to manifest
+					maniyaml.Packages[pkgName].Triggers[trg.Name] = *maniyaml.ComposeParsersTrigger(trg)
 				}
 			}
 		}
diff --git a/cmd/root.go b/cmd/root.go
index c29eca12..9eb835c2 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -129,6 +129,7 @@ func init() {
 	RootCmd.PersistentFlags().StringVarP(&utils.Flags.ProjectName, "projectname", "", "", wski18n.T(wski18n.ID_CMD_FLAG_PROJECTNAME))
 	RootCmd.PersistentFlags().BoolVarP(&utils.Flags.Trace, "trace", "t", false, wski18n.T(wski18n.ID_CMD_FLAG_TRACE))
 	RootCmd.PersistentFlags().MarkHidden("trace")
+	RootCmd.PersistentFlags().StringVarP(&utils.Flags.RelationsPath, "relationships", "r", "", "projects reationships manifest")
 }
 
 // initConfig reads in config file and ENV variables if set.
@@ -327,17 +328,32 @@ func Undeploy() error {
 
 		// The auth, apihost and namespace have been chosen, so that we can check the supported runtimes here.
 		setSupportedRuntimes(clientConfig.Host)
-
 		verifiedPlan, err := deployer.ConstructUnDeploymentPlan()
 		if err != nil {
 			return err
 		}
-
 		err = deployer.UnDeploy(verifiedPlan)
 		if err != nil {
 			return err
-		} else {
-			return nil
+		}
+
+		// get the relationships update plan
+		// update relationships
+		if utils.Flags.RelationsPath != "" && utils.FileExists(utils.Flags.RelationsPath) {
+			// reset deployer project
+			deployer.Deployment = deployers.NewDeploymentProject()
+
+			// update project with assets with updated relationships
+			err = deployer.UpdateManagedProjectsListAnnotations(true)
+			if err != nil {
+				return err
+			}
+
+			// deploy updated assets
+			err = deployer.UndeployRelationships()
+			if err != nil {
+				return err
+			}
 		}
 
 	} else {
@@ -345,4 +361,6 @@ func Undeploy() error {
 			map[string]interface{}{wski18n.KEY_PATH: utils.Flags.ManifestPath})
 		return wskderrors.NewErrorManifestFileNotFound(utils.Flags.ManifestPath, errString)
 	}
+
+	return nil
 }
diff --git a/deployers/servicedeployer.go b/deployers/servicedeployer.go
index 7fa8abed..06c111fd 100644
--- a/deployers/servicedeployer.go
+++ b/deployers/servicedeployer.go
@@ -201,6 +201,11 @@ func (deployer *ServiceDeployer) ConstructDeploymentPlan() error {
 		}
 	}
 
+	// process relationships file
+	if utils.Flags.RelationsPath != "" && utils.FileExists(utils.Flags.RelationsPath) {
+		err = deployer.UpdateManagedProjectsListAnnotations(false)
+	}
+
 	return err
 }
 
@@ -216,6 +221,8 @@ func (deployer *ServiceDeployer) ConstructUnDeploymentPlan() (*DeploymentProject
 
 	manifestReader.InitPackages(manifestParser, manifest, whisk.KeyValue{})
 
+	deployer.ProjectName = manifest.GetProject().Name
+
 	// process manifest file
 	err = manifestReader.HandleYaml(deployer, manifestParser, manifest, whisk.KeyValue{})
 	if err != nil {
@@ -349,6 +356,15 @@ func (deployer *ServiceDeployer) deployAssets() error {
 			whisk.Debug(whisk.DbgError, errString)
 			return err
 		}
+
+		if utils.Flags.RelationsPath != "" {
+			ma := deployer.ManagedAnnotation.Value.(map[string]interface{})
+			if err := deployer.RefreshRelationships(ma); err != nil {
+				errString := wski18n.T(wski18n.ID_MSG_MANAGED_UNDEPLOYMENT_FAILED)
+				whisk.Debug(whisk.DbgError, errString)
+				return err
+			}
+		}
 	}
 
 	return nil
@@ -1589,3 +1605,449 @@ func createWhiskClientError(err *whisk.WskError, response *http.Response, entity
 	// TODO() add errString as an AppendDetail() to WhiskClientError
 	return wskderrors.NewWhiskClientError(err.Error(), err.ExitCode, response)
 }
+
+// for all projects in relationships
+// 	 for all rel-packages in rel-project
+//		get ow-package from OW
+// 			if rel-project is the one in ow-package MANAGE annotation (library project)
+//				update MANAGE_LIST annotation with NEW project and add to deploymentPlan
+//			for all actions in rel-package
+//				for all ow-actions in ow-package
+// 					if rel-project is the one in ow-action MANAGE annotation (library project)
+//						update MANAGE_LIST annotation with NEW project and add to deploymentPlan
+//
+// ... same for triggers and rules
+func (deployer *ServiceDeployer) UpdateManagedProjectsListAnnotations(isUndeploy bool) error {
+	if utils.Flags.RelationsPath == "" {
+		return nil
+	} else if !utils.FileExists(utils.Flags.RelationsPath) {
+		// TODO: change to correct error
+		return wskderrors.NewCommandError("", "relationships file doesnt exists")
+	}
+
+	rma, err := utils.GenerateManagedAnnotation(deployer.ProjectName, utils.Flags.RelationsPath)
+
+	mm := parsers.NewYAMLParser()
+	relationships, err := mm.ParseRelations(utils.Flags.RelationsPath)
+	if err != nil {
+		return err
+	}
+
+	// get managed annotations
+	for managedProjectName, proj := range relationships.Projects {
+		for managedPackageName, pkg := range proj.Packages {
+			updatedPackage, err := deployer.getUpdatedPackage(managedPackageName, managedProjectName, rma, isUndeploy)
+
+			if err != nil {
+				return err
+			}
+
+			// update actions
+			for actionName := range pkg.Actions {
+				updatedAction, err := deployer.getUpdatedAction(managedPackageName, actionName, managedProjectName, rma, isUndeploy)
+
+				if err != nil {
+					return err
+				}
+
+				if updatedAction != nil {
+					updatedPackage.Actions[actionName] = *updatedAction
+				}
+			}
+
+			// add updated package to the deployment plan
+			deployer.Deployment.Packages[managedPackageName] = updatedPackage
+
+			// update triggers
+			for triggerName := range pkg.Triggers {
+				trigger, err := deployer.getUpdatedTrigger(managedPackageName, triggerName, managedProjectName, rma, isUndeploy)
+
+				if err != nil {
+					return err
+				}
+
+				if trigger != nil {
+					deployer.Deployment.Triggers[triggerName] = trigger
+				}
+			}
+
+			// update rules
+			for ruleName := range pkg.Rules {
+				rule, err := deployer.getUpdatedRule(managedPackageName, ruleName, managedProjectName, rma, isUndeploy)
+
+				if err != nil {
+					return err
+				}
+
+				if rule != nil {
+					deployer.Deployment.Rules[ruleName] = rule
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+// update annotation holding asset managed project list
+// in case annotation doesn't exist
+// create MANAGED_LIST annotation
+//		append OLD MANAGED and new project to the MANAGED_LIST
+// in case project present in the list
+//		do nothing
+func (deployer *ServiceDeployer) getUpdatedAnnotations(whiskAnnotations whisk.KeyValueArr, rma whisk.KeyValue, isUndeploy bool) whisk.KeyValueArr {
+	ma := rma.Value.(map[string]interface{})
+	if annotation := whiskAnnotations.GetValue(utils.MANAGED_LIST); annotation != nil {
+		mListIndex := whiskAnnotations.FindKeyValue(utils.MANAGED_LIST)
+
+		managedList := annotation.([]interface{})
+		for i, a := range managedList {
+			managed := a.(map[string]interface{})
+			if managed[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] {
+				// found current project in the list
+				if isUndeploy {
+					// undeploying the project. removing the project annotation from the managed list
+					managedList = managedList[:i+copy(managedList[i:], managedList[i+1:])]
+
+					// check if there left any managed project in the list
+					if len(managedList) == 0 {
+						// remove the MANAGED_LIST annotation from the asset
+						whiskAnnotations = whiskAnnotations[:mListIndex+copy(whiskAnnotations[mListIndex:], whiskAnnotations[mListIndex+1:])]
+					} else {
+						// update the managed list annotation with modified one
+						whiskAnnotations[mListIndex] = whisk.KeyValue{Key: utils.MANAGED_LIST, Value: managedList}
+					}
+
+					return whiskAnnotations
+				} else if managed[utils.OW_PROJECT_HASH] == ma[utils.OW_PROJECT_HASH] {
+					// the project already in managed projects list, no need to update anything
+					return nil
+				} else {
+					// update project hash. the asset appears in the manifest so we dont want to remove it from ow
+					managed[utils.OW_PROJECT_HASH] = ma[utils.OW_PROJECT_HASH]
+					managedList[i] = managed
+					whiskAnnotations[mListIndex] = whisk.KeyValue{Key: utils.MANAGED_LIST, Value: managedList}
+					return whiskAnnotations
+				}
+			}
+		}
+
+		// if we are here, it means that current managed project is not in the list
+		managedList = append(annotation.([]interface{}), ma)
+		whiskAnnotations[mListIndex] = whisk.KeyValue{Key: utils.MANAGED_LIST, Value: managedList}
+		return whiskAnnotations
+	} else if !isUndeploy {
+		aa := make([]interface{}, 1)
+		aa[0] = ma
+
+		return append(whiskAnnotations, whisk.KeyValue{Key: utils.MANAGED_LIST, Value: aa})
+	}
+	return nil
+}
+
+// Find MANAGED_LIST annotation
+//   In case found -
+//		If relations project hash differs from the current one remove - from the list
+//      In case length == 0 remove MANAGED_LIST annotation from the annotations
+func (deployer *ServiceDeployer) RefreshManagedProjectsListAnnotation(whiskAnnotations whisk.KeyValueArr) whisk.KeyValueArr {
+	if utils.Flags.RelationsPath == "" {
+		return nil
+	}
+
+	rma, _ := utils.GenerateManagedAnnotation(deployer.ProjectName, utils.Flags.RelationsPath)
+	ma := rma.Value.(map[string]interface{})
+
+	if annotation := whiskAnnotations.GetValue(utils.MANAGED_LIST); annotation != nil {
+		managedList := annotation.([]interface{})
+		// find managed project index
+		for i, a := range managedList {
+			managed := a.(map[string]interface{})
+
+			if managed[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && managed[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] {
+				// remove managed project from the list
+				managedList = managedList[:i+copy(managedList[i:], managedList[i+1:])]
+
+				// update MANAGED value to the first one in the MANAGED_LIST
+				//				i = whiskAnnotations.FindKeyValue(utils.MANAGED)
+				//				whiskAnnotations[i] = whisk.KeyValue{Key: utils.MANAGED, Value: managedList[0]}
+
+				i = whiskAnnotations.FindKeyValue(utils.MANAGED_LIST)
+				// check if there left single managed project in the list
+				if len(managedList) == 0 {
+					// remove the MANAGED_LIST annotation from the asset
+					whiskAnnotations = whiskAnnotations[:i+copy(whiskAnnotations[i:], whiskAnnotations[i+1:])]
+				} else {
+					// update the managed list annotation with modified one
+					whiskAnnotations[i] = whisk.KeyValue{Key: utils.MANAGED_LIST, Value: managedList}
+				}
+
+				return whiskAnnotations
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func (deployer *ServiceDeployer) getUpdatedPackage(packageName string, projectName string, rma whisk.KeyValue, isUndeploy bool) (*DeploymentPackage, error) {
+	for _, pack := range deployer.Deployment.Packages {
+		if pack.Package.GetName() == packageName {
+			return nil, wskderrors.NewCommandError("", "asset can't be specified both in relationships and in manifest")
+		}
+	}
+
+	// get package from OW
+	owPkg, _, err := deployer.Client.Packages.Get(packageName)
+	if err != nil {
+		return nil, err
+	}
+
+	//validate that package MANAGER identical to the one specified in the relationships.yaml
+	managedProjectAnnotation := owPkg.Annotations.GetValue(utils.MANAGED).(map[string]interface{})
+	if managedProjectAnnotation == nil || managedProjectAnnotation[utils.OW_PROJECT_NAME] != projectName {
+		// TODO: change to correct error
+		return nil, wskderrors.NewCommandError("", "relationships file incorrect. Package "+packageName+" expected to be managed by "+projectName)
+	}
+
+	// update managed projects list annotations with new (current) project name if needed
+	annotations := deployer.getUpdatedAnnotations(owPkg.Annotations, rma, isUndeploy)
+
+	if annotations == nil {
+		fmt.Println("no need to update this asset annotations")
+	} else {
+		owPkg.Annotations = annotations
+	}
+
+	deploymentPackage := NewDeploymentPackage()
+	deploymentPackage.Package = owPkg
+
+	return deploymentPackage, nil
+}
+
+func (deployer *ServiceDeployer) getUpdatedAction(packageName string, actionName string, projectName string, rma whisk.KeyValue, isUndeploy bool) (*utils.ActionRecord, error) {
+	if packageName != parsers.DEFAULT_PACKAGE {
+		actionName = strings.Join([]string{packageName, actionName}, "/")
+	}
+
+	// get action from OW
+	owAction, _, err := deployer.Client.Actions.Get(actionName, true)
+	if err != nil {
+		return nil, err
+	}
+
+	// update managed annotations with new project name if needed
+	owAction.Annotations = deployer.getUpdatedAnnotations(owAction.Annotations, rma, isUndeploy)
+
+	if owAction.Annotations != nil {
+		// creating action record with new annotation
+		var record utils.ActionRecord
+		record.Action = owAction
+		record.Packagename = packageName
+		return &record, nil
+	}
+
+	return nil, nil
+}
+
+func (deployer *ServiceDeployer) getUpdatedTrigger(packageName string, triggerName string, projectName string, rma whisk.KeyValue, isUndeploy bool) (*whisk.Trigger, error) {
+	// get action from OW
+	trigger, _, err := deployer.Client.Triggers.Get(triggerName)
+	if err != nil {
+		return nil, err
+	}
+
+	// update managed annotations with new project name if needed
+	trigger.Annotations = deployer.getUpdatedAnnotations(trigger.Annotations, rma, isUndeploy)
+
+	if trigger.Annotations != nil {
+		return trigger, nil
+	}
+
+	return nil, nil
+}
+
+func (deployer *ServiceDeployer) getUpdatedRule(packageName string, ruleName string, projectName string, rma whisk.KeyValue, isUndeploy bool) (*whisk.Rule, error) {
+	fmt.Println(ruleName)
+
+	// get action from OW
+	rule, _, err := deployer.Client.Rules.Get(ruleName)
+	if err != nil {
+		return nil, err
+	}
+
+	// update managed annotations with new project name if needed
+	rule.Annotations = deployer.getUpdatedAnnotations(rule.Annotations, rma, isUndeploy)
+
+	//TODO: raise issue for the workaround below. Trigger name should be parsed out to short trigger name
+	rule.Trigger = "/" + rule.Trigger.(map[string]interface{})["path"].(string) + "/" + rule.Trigger.(map[string]interface{})["name"].(string)
+	rule.Action = "/" + rule.Action.(map[string]interface{})["path"].(string) + "/" + rule.Action.(map[string]interface{})["name"].(string)
+
+	if rule.Annotations != nil {
+		return rule, nil
+	}
+
+	return nil, nil
+}
+
+func (deployer *ServiceDeployer) RefreshRelationships(ma map[string]interface{}) error {
+	// Get the list of packages in your namespace
+	packages, _, err := deployer.Client.Packages.List(&whisk.PackageListOptions{})
+	if err != nil {
+		return err
+	}
+
+	// iterate over each package to find and update managed_list annotations
+	for _, pkg := range packages {
+		if pkg.Annotations.GetValue(utils.MANAGED_LIST) != nil {
+			if a := deployer.RefreshManagedProjectsListAnnotation(pkg.Annotations); a != nil {
+				pkg.Annotations = a
+
+				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+					_, _, err := deployer.Client.Packages.Insert(&pkg, true)
+					return err
+				})
+
+				if err != nil {
+					return err
+				}
+			}
+
+			actions, _, err := deployer.Client.Actions.List(pkg.Name, &whisk.ActionListOptions{})
+			if err != nil {
+				return err
+			}
+
+			for _, action := range actions {
+				if action.Annotations.GetValue(utils.MANAGED_LIST) != nil {
+					if a := deployer.RefreshManagedProjectsListAnnotation(action.Annotations); a != nil {
+						actionName := strings.Join([]string{pkg.Name, action.Name}, "/")
+						// get action from OW
+						owAction, _, err := deployer.Client.Actions.Get(actionName, true)
+						if err != nil {
+							return err
+						}
+						owAction.Annotations = a
+						owAction.Name = actionName
+						fmt.Println(owAction.Exec)
+						err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+							_, _, err := deployer.Client.Actions.Insert(owAction, true)
+							return err
+						})
+					}
+				}
+
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	// Get list of triggers in your namespace
+	triggers, _, err := deployer.Client.Triggers.List(&whisk.TriggerListOptions{})
+	if err != nil {
+		return err
+	}
+
+	for _, trigger := range triggers {
+		var err error
+
+		if trigger.Annotations.GetValue(utils.MANAGED_LIST) != nil {
+			if a := deployer.RefreshManagedProjectsListAnnotation(trigger.Annotations); a != nil {
+				trigger.Annotations = a
+
+				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+					_, _, err := deployer.Client.Triggers.Insert(&trigger, true)
+					return err
+				})
+			}
+		}
+
+		if err != nil {
+			return err
+		}
+
+	}
+
+	// Get list of rules in your namespace
+	rules, _, err := deployer.Client.Rules.List(&whisk.RuleListOptions{})
+	if err != nil {
+		return err
+	}
+
+	for _, rule := range rules {
+		var err error
+
+		// rule has attached managed list annotation
+		if rule.Annotations.GetValue(utils.MANAGED_LIST) != nil {
+			if a := deployer.RefreshManagedProjectsListAnnotation(rule.Annotations); a != nil {
+				rule.Annotations = a
+
+				err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+					_, _, err := deployer.Client.Rules.Insert(&rule, true)
+					return err
+				})
+			}
+		}
+		if err != nil {
+			return err
+		}
+
+	}
+
+	return err
+}
+
+func (deployer *ServiceDeployer) UndeployRelationships() error {
+	var err error
+
+	for packageName, pack := range deployer.Deployment.Packages {
+		pkg := pack.Package
+		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+			_, _, err := deployer.Client.Packages.Insert(pkg, true)
+			return err
+		})
+
+		if err != nil {
+			return err
+		}
+
+		for _, action := range pack.Actions {
+			actionName := strings.Join([]string{packageName, action.Action.Name}, "/")
+			action.Action.Name = actionName
+			err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+				_, _, err := deployer.Client.Actions.Insert(action.Action, true)
+				return err
+			})
+
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	for _, rule := range deployer.Deployment.Rules {
+		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+			_, _, err := deployer.Client.Rules.Insert(rule, true)
+			return err
+		})
+
+		if err != nil {
+			return err
+		}
+	}
+
+	for _, trigger := range deployer.Deployment.Triggers {
+		err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error {
+			_, _, err := deployer.Client.Triggers.Insert(trigger, true)
+			return err
+		})
+
+		if err != nil {
+			return err
+		}
+	}
+
+	return err
+}
diff --git a/parsers/deploy_parser.go b/parsers/deploy_parser.go
index 8f985fed..f794db71 100644
--- a/parsers/deploy_parser.go
+++ b/parsers/deploy_parser.go
@@ -31,6 +31,14 @@ func (dm *YAMLParser) unmarshalDeployment(input []byte, deploy *YAML) error {
 	return nil
 }
 
+func (dm *YAMLParser) unmarshalRelations(input []byte, deploy *PROJECTS) error {
+	err := yaml.UnmarshalStrict(input, deploy)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func (dm *YAMLParser) ParseDeployment(deploymentPath string) (*YAML, error) {
 	dplyyaml := YAML{}
 	content, err := new(utils.ContentReader).LocalReader.ReadLocal(deploymentPath)
@@ -50,6 +58,23 @@ func (dm *YAMLParser) ParseDeployment(deploymentPath string) (*YAML, error) {
 	return dplyyamlEnvVar, nil
 }
 
+func (dm *YAMLParser) ParseRelations(deploymentPath string) (*PROJECTS, error) {
+	dplyyaml := PROJECTS{}
+	content, err := new(utils.ContentReader).LocalReader.ReadLocal(deploymentPath)
+
+	if err != nil {
+		return &dplyyaml, wskderrors.NewFileReadError(deploymentPath, err.Error())
+	}
+
+	err = dm.unmarshalRelations(content, &dplyyaml)
+
+	if err != nil {
+		return &dplyyaml, wskderrors.NewYAMLParserErr(deploymentPath, err)
+	}
+
+	return &dplyyaml, nil
+}
+
 //********************Project functions*************************//
 //This is for parse the deployment yaml file.
 func (app *Project) GetPackageList() []Package {
diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go
index 3a626e70..116d5302 100644
--- a/parsers/manifest_parser.go
+++ b/parsers/manifest_parser.go
@@ -20,12 +20,13 @@ package parsers
 import (
 	"encoding/base64"
 	"errors"
-	"gopkg.in/yaml.v2"
 	"io/ioutil"
 	"os"
 	"path"
 	"strings"
 
+	"gopkg.in/yaml.v2"
+
 	"github.com/apache/incubator-openwhisk-client-go/whisk"
 	"github.com/apache/incubator-openwhisk-wskdeploy/utils"
 	"github.com/apache/incubator-openwhisk-wskdeploy/wskderrors"
@@ -994,7 +995,7 @@ func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(client *whisk.Config, man
 func (dm *YAMLParser) ComposeApiRecords(client *whisk.Config, packageName string, pkg Package, manifestPath string) ([]*whisk.ApiCreateRequest, error) {
 	var requests []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0)
 
-	if pkg.Apis != nil {
+	if pkg.Apis != nil && len(pkg.Apis) > 0 {
 		// verify APIGW_ACCESS_TOKEN is set before composing APIs
 		// until this point, we dont know whether APIs are specified in manifest or not
 		if len(client.ApigwAccessToken) == 0 {
diff --git a/parsers/yamlparser.go b/parsers/yamlparser.go
index b8a58b6b..c0b9284e 100644
--- a/parsers/yamlparser.go
+++ b/parsers/yamlparser.go
@@ -229,6 +229,10 @@ type YAML struct {
 	Filepath string             //file path of the yaml file
 }
 
+type PROJECTS struct {
+	Projects map[string]YAML `yaml:"projects"` //used in relationships.yaml
+}
+
 // function to return Project or Application depending on what is specified in
 // manifest and deployment files
 func (yaml *YAML) GetProject() Project {
@@ -403,6 +407,9 @@ func (yaml *YAML) ComposeParsersTrigger(wsktrg whisk.Trigger) *Trigger {
 	trigger := new(Trigger)
 	trigger.Name = wsktrg.Name
 	trigger.Namespace = wsktrg.Namespace
+	if a := wsktrg.Annotations.GetValue(YAML_KEY_FEED); a != nil {
+		trigger.Feed = a.(string)
+	}
 
 	for _, keyval := range wsktrg.Parameters {
 		param := new(Parameter)
diff --git a/tests/src/integration/common/wskdeploy.go b/tests/src/integration/common/wskdeploy.go
index 82a34048..60310665 100644
--- a/tests/src/integration/common/wskdeploy.go
+++ b/tests/src/integration/common/wskdeploy.go
@@ -21,17 +21,18 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strings"
+
 	"github.com/apache/incubator-openwhisk-client-go/whisk"
 	"github.com/apache/incubator-openwhisk-wskdeploy/deployers"
 	"github.com/apache/incubator-openwhisk-wskdeploy/utils"
 	"github.com/apache/incubator-openwhisk-wskdeploy/wskderrors"
 	"github.com/fatih/color"
 	"github.com/mattn/go-colorable"
-	"os"
-	"os/exec"
-	"path"
-	"path/filepath"
-	"strings"
 )
 
 const (
@@ -178,6 +179,14 @@ func (Wskdeploy *Wskdeploy) ManagedUndeployment(manifestPath string, deploymentP
 	return Wskdeploy.RunCommand("undeploy", "-m", manifestPath, "-d", deploymentPath, "--managed")
 }
 
+func (wskdeploy *Wskdeploy) ManagedRelationshipsDeployment(manifestPath string, relationshipsPath string) (string, error) {
+	return wskdeploy.RunCommand("-m", manifestPath, "-r", relationshipsPath, "--managed")
+}
+
+func (wskdeploy *Wskdeploy) ManagedRelationshipsUnDeployment(manifestPath string, relationshipsPath string) (string, error) {
+	return wskdeploy.RunCommand("undeploy", "-m", manifestPath, "-r", relationshipsPath, "--managed")
+}
+
 // This method is only for testing
 // This method will mock a construction of deployment plan, creating all the memory objects
 // This method CANNOT be used for real deployment!
diff --git a/tests/src/integration/relationships/README.md b/tests/src/integration/relationships/README.md
new file mode 100644
index 00000000..4ea7707d
--- /dev/null
+++ b/tests/src/integration/relationships/README.md
@@ -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.
+#
+-->
+
+# Test Case of projects' relationships
+
+This is a test case for relationships between a project and a library project.
+It can be deployed and tested with:
+
+```bash
+$ wskdeploy -p tests/src/integration/relationships
+```
diff --git a/tests/src/integration/relationships/deployment.yml b/tests/src/integration/relationships/deployment.yml
new file mode 100644
index 00000000..6f9e2141
--- /dev/null
+++ b/tests/src/integration/relationships/deployment.yml
@@ -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.
+#
+
+project:
+  name: wskdeploy-samples
+
+  packages:
+    helloWorldTriggerRule:
+      actions:
+        greeting:
+          inputs:
+            name: Bernie
+            place: Vermont
diff --git a/tests/src/integration/relationships/ext_manifest.yml b/tests/src/integration/relationships/ext_manifest.yml
new file mode 100644
index 00000000..4000fefa
--- /dev/null
+++ b/tests/src/integration/relationships/ext_manifest.yml
@@ -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.
+#
+project:
+  name: EXT
+packages:
+  triggerrule_ext:
+      version: 1.0
+      license: Apache-2.0
+      actions:
+        greeting:
+          version: 1.0
+          function: src/greeting.js
+          runtime: nodejs:6
diff --git a/tests/src/integration/relationships/manifest.yml b/tests/src/integration/relationships/manifest.yml
new file mode 100644
index 00000000..a8a412bd
--- /dev/null
+++ b/tests/src/integration/relationships/manifest.yml
@@ -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.
+#
+project:
+  name: LIB
+packages:
+  triggerrule:
+      version: 1.0
+      license: Apache-2.0
+      actions:
+        greeting:
+          version: 1.0
+          function: src/greeting.js
+          runtime: nodejs:6
+      triggers:
+        locationUpdate:
+      rules:
+        myRule:
+          trigger: locationUpdate
+          action: greeting
diff --git a/tests/src/integration/relationships/relationships.yml b/tests/src/integration/relationships/relationships.yml
new file mode 100644
index 00000000..f3133462
--- /dev/null
+++ b/tests/src/integration/relationships/relationships.yml
@@ -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.
+#
+projects: 
+  LIB: 
+    packages: 
+      triggerrule: 
+        actions: 
+          greeting: ~
+        triggers: 
+          locationUpdate: ~
\ No newline at end of file
diff --git a/tests/src/integration/relationships/relationships_test.go b/tests/src/integration/relationships/relationships_test.go
new file mode 100644
index 00000000..cd373dae
--- /dev/null
+++ b/tests/src/integration/relationships/relationships_test.go
@@ -0,0 +1,52 @@
+// +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"
+)
+
+/* *
+ * Please configure BLUEMIX_APIHOST, BLUEMIX_NAMESPACE and BLUEMIX_AUTH on your local machine in order to run this
+ * integration test.
+ */
+func TestRelationships(t *testing.T) {
+	wskdeploy := common.NewWskdeploy()
+
+	_, err := wskdeploy.ManagedDeployment(manifestLibPath, deploymentLibPath)
+	assert.Equal(t, nil, err, "Failed to deploy the lib manifest file.")
+	_, err = wskdeploy.ManagedRelationshipsDeployment(manifestExtPath, relationshipsPath)
+	assert.Equal(t, nil, err, "Failed to deploy the ext manifest file.")
+	_, err = wskdeploy.ManagedRelationshipsUnDeployment(manifestExtPath, relationshipsPath)
+	assert.Equal(t, nil, err, "Failed to undeploy the ext manifest file.")
+	_, err = wskdeploy.ManagedUndeployment(manifestLibPath, deploymentLibPath)
+	assert.Equal(t, nil, err, "Failed to undeploy the lib manifest file.")
+}
+
+var (
+	manifestLibPath   = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/relationships/manifest.yml"
+	manifestExtPath   = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/relationships/ext_manifest.yml"
+	deploymentLibPath = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/relationships/deployment.yml"
+	relationshipsPath = os.Getenv("GOPATH") + "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/relationships/relationships.yml"
+)
diff --git a/tests/src/integration/relationships/src/greeting.js b/tests/src/integration/relationships/src/greeting.js
new file mode 100644
index 00000000..5c01d98b
--- /dev/null
+++ b/tests/src/integration/relationships/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/usecases/relationships/README.md b/tests/usecases/relationships/README.md
new file mode 100644
index 00000000..c36409f1
--- /dev/null
+++ b/tests/usecases/relationships/README.md
@@ -0,0 +1,184 @@
+<!--
+#
+# 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.
+#
+-->
+
+# Relationships use case for wskdeploy
+
+### Package description
+
+This Package named `relationships` includes:
+- A manifest.yaml represents library project LIB
+- An ext_manifest.yml represents an EXT project that uses assets (lib_trigger) from project LIB
+- relationships.yml describes the projects' relationships
+
+
+### How to deploy and test
+
+#### Step 1. Deploy the LIB package.
+
+```
+$ wskdeploy -m tests/usecases/relationships/manifest.yml --managed
+```
+
+#### Step 2. Verify the assets have been installed.
+
+e.g. 
+```
+$ wsk package get lib_package
+{
+    "name": "lib_package",
+    "annotations": [
+        {
+            "key": "managed",
+            "value": {
+                "__OW_FILE": "tests/usecases/relationships/manifest.yml",
+                "__OW_PROJECT_HASH": "34d92ecfcf71c5ec2d3c2936ae9415c755b05158",
+                "__OW_PROJECT_NAME": "LIB"
+            }
+        }
+    ],
+    "actions": [
+        {
+            "name": "lib_greeting_2",
+            "annotations": [
+                {
+                    "key": "managed",
+                    "value": {
+                        "__OW_FILE": "tests/usecases/relationships/manifest.yml",
+                        "__OW_PROJECT_HASH": "34d92ecfcf71c5ec2d3c2936ae9415c755b05158",
+                        "__OW_PROJECT_NAME": "LIB"
+                    }
+                },
+                {
+                    "key": "exec",
+                    "value": "nodejs:6"
+                }
+            ]
+        },
+        {
+            "name": "lib_greeting_1",
+            "version": "0.0.1",
+            "annotations": [
+                {
+                    "key": "managed",
+                    "value": {
+                        "__OW_FILE": "tests/usecases/relationships/manifest.yml",
+                        "__OW_PROJECT_HASH": "34d92ecfcf71c5ec2d3c2936ae9415c755b05158",
+                        "__OW_PROJECT_NAME": "LIB"
+                    }
+                },
+                {
+                    "key": "exec",
+                    "value": "nodejs:6"
+                }
+            ]
+        }
+    ]
+}
+```
+
+#### Step 3. Deploy the EXT package.
+
+```
+$ wskdeploy -m tests/usecases/relationships/ext_manifest.yml -r tests/usecases/relationships/relationships.yml --managed
+```
+
+#### Step 4. Verify the assets relationships have been updated.
+
+e.g. 
+```
+$ wsk package get lib_package
+ok: got package lib_package
+{
+    "annotations": [
+        {
+            "key": "managed",
+            "value": {
+                "__OW_FILE": "tests/usecases/relationships/manifest.yml",
+                "__OW_PROJECT_HASH": "34d92ecfcf71c5ec2d3c2936ae9415c755b05158",
+                "__OW_PROJECT_NAME": "LIB"
+            }
+        },
+        {
+            "key": "managed_list",
+            "value": [
+                {
+                    "__OW_FILE": "tests/usecases/relationships/relationships.yml",
+                    "__OW_PROJECT_HASH": "512917c4df9a0acaa0aa339801526fb810b0ee65",
+                    "__OW_PROJECT_NAME": "EXT"
+                }
+            ]
+        }
+    ],
+    "actions": [
+        {
+            "name": "lib_greeting_2",
+            "annotations": [
+                {
+                    "key": "managed",
+                    "value": {
+                        "__OW_FILE": "tests/usecases/relationships/manifest.yml",
+                        "__OW_PROJECT_HASH": "34d92ecfcf71c5ec2d3c2936ae9415c755b05158",
+                        "__OW_PROJECT_NAME": "LIB"
+                    }
+                },
+                {
+                    "key": "exec",
+                    "value": "nodejs:6"
+                },
+                {
+                    "key": "managed_list",
+                    "value": [
+                        {
+                            "__OW_FILE": "tests/usecases/relationships/relationships.yml",
+                            "__OW_PROJECT_HASH": "512917c4df9a0acaa0aa339801526fb810b0ee65",
+                            "__OW_PROJECT_NAME": "EXT"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "lib_greeting_1",
+            "annotations": [
+                {
+                    "key": "managed",
+                    "value": {
+                        "__OW_FILE": "tests/usecases/relationships/manifest.yml",
+                        "__OW_PROJECT_HASH": "34d92ecfcf71c5ec2d3c2936ae9415c755b05158",
+                        "__OW_PROJECT_NAME": "LIB"
+                    }
+                },
+                {
+                    "key": "exec",
+                    "value": "nodejs:6"
+                }
+            ]
+        }
+    ]
+}
+
+
+```
+
+#### Step 5. Export EXT project
+
+```
+$ wskdeploy export -p EXT -m exported_ext.yaml --managed
+
+explore exported_ext.yaml manifest file and notice both EXT and LIB project assets are there without any notion of the LIB project
+```
diff --git a/tests/usecases/relationships/ext_manifest.yml b/tests/usecases/relationships/ext_manifest.yml
new file mode 100644
index 00000000..fd6792e3
--- /dev/null
+++ b/tests/usecases/relationships/ext_manifest.yml
@@ -0,0 +1,30 @@
+#
+# 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: EXT
+packages:
+  triggerrule_ext:
+      version: 1.0
+      license: Apache-2.0
+      actions:
+        ext_greeting:
+          version: 1.0
+          function: src/greeting.js
+          runtime: nodejs:6
+      rules:
+        ext_rule:
+          trigger: lib_trigger
+          action: ext_greeting
\ No newline at end of file
diff --git a/tests/usecases/relationships/manifest.yml b/tests/usecases/relationships/manifest.yml
new file mode 100644
index 00000000..b0366b3b
--- /dev/null
+++ b/tests/usecases/relationships/manifest.yml
@@ -0,0 +1,36 @@
+#
+# 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: LIB
+packages:
+  lib_package:
+      version: 1.0
+      license: Apache-2.0
+      actions:
+        lib_greeting_1:
+          version: 1.0
+          function: src/greeting.js
+          runtime: nodejs:6
+        lib_greeting_2:
+          version: 1.0
+          function: src/greeting.js
+          runtime: nodejs:6
+      triggers:
+        lib_trigger:
+      rules:
+        lib_rule:
+          trigger: lib_trigger
+          action: lib_greeting_1
\ No newline at end of file
diff --git a/tests/usecases/relationships/relationships.yml b/tests/usecases/relationships/relationships.yml
new file mode 100644
index 00000000..77b90888
--- /dev/null
+++ b/tests/usecases/relationships/relationships.yml
@@ -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.
+#
+projects: 
+  LIB: 
+    packages: 
+      lib_package: 
+        actions: 
+          lib_greeting_2: ~
+        triggers: 
+          lib_trigger: ~
\ No newline at end of file
diff --git a/tests/usecases/relationships/src/greeting.js b/tests/usecases/relationships/src/greeting.js
new file mode 100644
index 00000000..5c01d98b
--- /dev/null
+++ b/tests/usecases/relationships/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/usecases/triggerrule/manifest.yml b/tests/usecases/triggerrule/manifest.yml
index 7f486bcb..ca0ed7a6 100644
--- a/tests/usecases/triggerrule/manifest.yml
+++ b/tests/usecases/triggerrule/manifest.yml
@@ -13,7 +13,8 @@
 # CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations under the License.
 #
-
+project:
+  name: AAA
 packages:
   triggerrule:
       version: 1.0
@@ -21,16 +22,11 @@ packages:
       actions:
         greeting:
           version: 1.0
-          function: src/greeting.js
+          function: greeting.js
           runtime: nodejs:6
-          inputs:
-            name: string
-            place: string
-          outputs:
-            payload: string
       triggers:
         locationUpdate:
       rules:
         myRule:
           trigger: locationUpdate
-          action: greeting
+          action: greeting
\ No newline at end of file
diff --git a/utils/flags.go b/utils/flags.go
index 9232fc03..93e9b604 100644
--- a/utils/flags.go
+++ b/utils/flags.go
@@ -43,6 +43,7 @@ type WskDeployFlags struct {
 	ApigwAccessToken string
 	Verbose          bool
 	Trace            bool
+	RelationsPath    string
 }
 
 func (flags *WskDeployFlags) Format() string {
diff --git a/utils/managedannotations.go b/utils/managedannotations.go
index fa0781ee..7a491e48 100644
--- a/utils/managedannotations.go
+++ b/utils/managedannotations.go
@@ -21,8 +21,9 @@ import (
 	"crypto/sha1"
 	"encoding/json"
 	"fmt"
-	"github.com/apache/incubator-openwhisk-client-go/whisk"
 	"os"
+
+	"github.com/apache/incubator-openwhisk-client-go/whisk"
 )
 
 /*
@@ -35,6 +36,7 @@ import (
  */
 
 const (
+	MANAGED_LIST    = "managed_list"
 	MANAGED         = "managed"
 	OPENWHISK       = "OpenWhisk"
 	NULL            = "golang\000"


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services