veyron/services/mgmt: Implement optional packages

This change implements optional packages in the mgmt services. The
binary repository now saves the media type and encoding of the objects,
e.g type=application/x-tar,encoding=gzip, or type=application/zip.

The node manager uses this information to download and install the
packages requested in the application envelopes.

TODO: Test the node manager implementation. This will require either using the
real binaryd, or significantly improving the mock binaryd.

Change-Id: Idc7d0ee7663ccd1816a271289de8f61403d8dcce
diff --git a/services/mgmt/node/impl/app_service.go b/services/mgmt/node/impl/app_service.go
index 794d9f0..0ad6e33 100644
--- a/services/mgmt/node/impl/app_service.go
+++ b/services/mgmt/node/impl/app_service.go
@@ -21,6 +21,10 @@
 //         bin                      - application binary
 //         previous                 - symbolic link to previous version directory
 //         envelope                 - application envelope (JSON-encoded)
+//         pkg/                     - the application packages
+//           <pkg name>
+//           <pkg name>.__info
+//           ...
 //       <version 2 timestamp>
 //       ...
 //       current                    - symbolic link to the current version
@@ -29,6 +33,9 @@
 //           credentials/           - holds veyron credentials (unless running
 //                                    through security agent)
 //           root/                  - workspace that the instance is run from
+//             packages/            - the installed packages
+//               <pkg name>/
+//               ...
 //           logs/                  - stderr/stdout and log files generated by instance
 //           info                   - metadata for the instance (such as app
 //                                    cycle manager name and process id)
@@ -136,6 +143,8 @@
 	vsecurity "veyron.io/veyron/veyron/security"
 	"veyron.io/veyron/veyron/security/agent"
 	"veyron.io/veyron/veyron/security/agent/keymgr"
+	libbinary "veyron.io/veyron/veyron/services/mgmt/lib/binary"
+	libpackages "veyron.io/veyron/veyron/services/mgmt/lib/packages"
 	iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
 )
 
@@ -319,10 +328,25 @@
 	if err := mkdir(versionDir); err != nil {
 		return "", errOperationFailed
 	}
+	pkgDir := filepath.Join(versionDir, "pkg")
+	if err := mkdir(pkgDir); err != nil {
+		return "", errOperationFailed
+	}
 	// TODO(caprita): Share binaries if already existing locally.
 	if err := downloadBinary(versionDir, "bin", envelope.Binary); err != nil {
 		return versionDir, err
 	}
+	for localPkg, pkgName := range envelope.Packages {
+		if localPkg == "" || localPkg[0] == '.' || strings.Contains(localPkg, string(filepath.Separator)) {
+			vlog.Infof("invalid local package name: %q", localPkg)
+			return versionDir, errOperationFailed
+		}
+		path := filepath.Join(pkgDir, localPkg)
+		if err := libbinary.DownloadToFile(rt.R().NewContext(), pkgName, path); err != nil {
+			vlog.Infof("DownloadToFile(%q, %q) failed: %v", pkgName, path, err)
+			return versionDir, errOperationFailed
+		}
+	}
 	if err := saveEnvelope(versionDir, envelope); err != nil {
 		return versionDir, err
 	}
@@ -554,6 +578,31 @@
 	return installationDirCore(i.suffix, i.config.Root)
 }
 
+// installPackages installs all the packages for a new instance.
+func installPackages(versionDir, instanceDir string) error {
+	envelope, err := loadEnvelope(versionDir)
+	if err != nil {
+		return err
+	}
+	packagesDir := filepath.Join(instanceDir, "root", "packages")
+	if err := os.MkdirAll(packagesDir, os.FileMode(0700)); err != nil {
+		return err
+	}
+	// TODO(rthellend): Consider making the packages read-only and sharing
+	// them between apps or instances.
+	for pkg, _ := range envelope.Packages {
+		pkgFile := filepath.Join(versionDir, "pkg", pkg)
+		dstDir := filepath.Join(packagesDir, pkg)
+		if err := os.MkdirAll(dstDir, os.FileMode(0700)); err != nil {
+			return err
+		}
+		if err := libpackages.Install(pkgFile, dstDir); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func initializeInstanceACLs(instanceDir string, blessings []string, acl security.ACL) error {
 	if acl.In == nil {
 		// The acl.In will be empty for an unclaimed node manager. In this case,
@@ -599,6 +648,10 @@
 		vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err)
 		return instanceDir, instanceID, errOperationFailed
 	}
+	if err := installPackages(versionDir, instanceDir); err != nil {
+		vlog.Errorf("installPackages(%v, %v) failed: %v", versionDir, instanceDir, err)
+		return instanceDir, instanceID, errOperationFailed
+	}
 	instanceInfo := new(instanceInfo)
 	if err := setupPrincipal(instanceDir, versionDir, call, i.securityAgent, instanceInfo); err != nil {
 		return instanceDir, instanceID, err