v2/services/mgmt,v/tools/mgmt/device: install package override (vdl+client)
This change modifies the device Install API to accept an override for packages
to install (on top of what's in the envelope). The motivation is to provide
packages like ACL files, which should not come from the publisher in the app
envelope, but rather be specified by the installer.
Part of this CL are also the client-side changes needed to support this,
specifically in the device command-line tool.
Server-side changes to come in a future CL.
Change-Id: Ic97db5163bc064a8cf56b9d62e68b68eaba6364d
diff --git a/services/mgmt/device/impl/app_service.go b/services/mgmt/device/impl/app_service.go
index 15fca5b..b82b74d 100644
--- a/services/mgmt/device/impl/app_service.go
+++ b/services/mgmt/device/impl/app_service.go
@@ -383,7 +383,7 @@
return versionDir, updateLink(versionDir, filepath.Join(installationDir, "current"))
}
-func (i *appService) Install(call ipc.ServerContext, applicationVON string, config device.Config) (string, error) {
+func (i *appService) Install(call ipc.ServerContext, applicationVON string, config device.Config, _ application.Packages) (string, error) {
if len(i.suffix) > 0 {
return "", verror.New(ErrInvalidSuffix, call.Context())
}
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index 34b28ba..b4ad8df 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -488,7 +488,7 @@
return nil
}
-func (*deviceService) Install(ctx ipc.ServerContext, _ string, _ device.Config) (string, error) {
+func (*deviceService) Install(ctx ipc.ServerContext, _ string, _ device.Config, _ application.Packages) (string, error) {
return "", verror.New(ErrInvalidSuffix, ctx.Context())
}
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index a42c5a0..4ec9bd3 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -1640,7 +1640,7 @@
},
},
}
- if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}); err != nil {
+ if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}, nil); err != nil {
t.Fatalf("Failed to Install app:%v", err)
}
@@ -1652,7 +1652,7 @@
if _, err := libbinary.Upload(ctx, naming.Join(binaryVON, "testbinary"), up, mediaInfo); err != nil {
t.Fatalf("Upload(%v) failed:%v", binaryVON, err)
}
- if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}); !verror.Is(err, impl.ErrOperationFailed.ID) {
+ if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}, nil); !verror.Is(err, impl.ErrOperationFailed.ID) {
t.Fatalf("Failed to verify signature mismatch for binary:%v", binaryVON)
}
@@ -1664,7 +1664,7 @@
if _, err := libbinary.Upload(ctx, naming.Join(binaryVON, "testbinary"), up, mediaInfo); err != nil {
t.Fatalf("Upload(%v) failed:%v", binaryVON, err)
}
- if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}); err != nil {
+ if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}, nil); err != nil {
t.Fatalf("Failed to Install app:%v", err)
}
@@ -1682,7 +1682,7 @@
if _, err = libbinary.UploadFromDir(ctx, pkgVON, tmpdir); err != nil {
t.Fatalf("libbinary.UploadFromDir failed: %v", err)
}
- if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}); !verror.Is(err, impl.ErrOperationFailed.ID) {
+ if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}, nil); !verror.Is(err, impl.ErrOperationFailed.ID) {
t.Fatalf("Failed to verify signature mismatch for package:%v", pkgVON)
}
}
diff --git a/services/mgmt/device/impl/util_test.go b/services/mgmt/device/impl/util_test.go
index abca341..6f13fc1 100644
--- a/services/mgmt/device/impl/util_test.go
+++ b/services/mgmt/device/impl/util_test.go
@@ -134,7 +134,7 @@
}
func installApp(t *testing.T, ctx *context.T, opt ...interface{}) string {
- appID, err := appStub().Install(ctx, mockApplicationRepoName, ocfg(opt))
+ appID, err := appStub().Install(ctx, mockApplicationRepoName, ocfg(opt), nil)
if err != nil {
t.Fatalf(testutil.FormatLogLine(2, "Install failed: %v", err))
}
@@ -142,7 +142,7 @@
}
func installAppExpectError(t *testing.T, ctx *context.T, expectedError verror.ID, opt ...interface{}) {
- if _, err := appStub().Install(ctx, mockApplicationRepoName, ocfg(opt)); err == nil || !verror.Is(err, expectedError) {
+ if _, err := appStub().Install(ctx, mockApplicationRepoName, ocfg(opt), nil); err == nil || !verror.Is(err, expectedError) {
t.Fatalf(testutil.FormatLogLine(2, "Install expected to fail with %v, got %v instead", expectedError, err))
}
}
diff --git a/tools/mgmt/device/doc.go b/tools/mgmt/device/doc.go
index abda7c0..ff4c731 100644
--- a/tools/mgmt/device/doc.go
+++ b/tools/mgmt/device/doc.go
@@ -86,6 +86,9 @@
-config={}
JSON-encoded device.Config object, of the form:
'{"flag1":"value1","flag2":"value2"}'
+ -packages={}
+ JSON-encoded application.Packages object, of the form:
+ '{"pkg1":{"File":"object name 1"},"pkg2":{"File":"object name 2"}}'
Device Install-Local
@@ -107,6 +110,9 @@
-config={}
JSON-encoded device.Config object, of the form:
'{"flag1":"value1","flag2":"value2"}'
+ -packages={}
+ JSON-encoded application.Packages object, of the form:
+ '{"pkg1":{"File":"object name 1"},"pkg2":{"File":"object name 2"}}'
Device Uninstall
diff --git a/tools/mgmt/device/impl/devicemanager_mock_test.go b/tools/mgmt/device/impl/devicemanager_mock_test.go
index 56c0ada..8dcebf6 100644
--- a/tools/mgmt/device/impl/devicemanager_mock_test.go
+++ b/tools/mgmt/device/impl/devicemanager_mock_test.go
@@ -21,7 +21,7 @@
"v.io/core/veyron2/vlog"
binlib "v.io/core/veyron/services/mgmt/lib/binary"
- "v.io/core/veyron/services/mgmt/lib/packages"
+ pkglib "v.io/core/veyron/services/mgmt/lib/packages"
)
type mockDeviceInvoker struct {
@@ -87,10 +87,12 @@
fun string
appName string
config device.Config
+ packages application.Packages
envelope application.Envelope
- // files holds a map from file or package name to file or package size.
- // The app binary has the key "binary". Each of the packages will have
- // the key "package/<package name>".
+ // files holds a map from file or package name to file or package size.
+ // The app binary has the key "binary". Each of the packages will have
+ // the key "package/<package name>". The override packages will have the
+ // key "overridepackage/<package name>".
files map[string]int64
}
@@ -131,8 +133,25 @@
}
}
-func (mni *mockDeviceInvoker) Install(call ipc.ServerContext, appName string, config device.Config) (string, error) {
- is := InstallStimulus{"Install", appName, config, application.Envelope{}, nil}
+func fetchPackageSize(ctx *context.T, pkgVON string) (int64, error) {
+ dir, err := ioutil.TempDir("", "package")
+ if err != nil {
+ return 0, fmt.Errorf("failed to create temp package dir: %v", err)
+ }
+ defer os.RemoveAll(dir)
+ tmpFile := filepath.Join(dir, "downloaded")
+ if err := binlib.DownloadToFile(ctx, pkgVON, tmpFile); err != nil {
+ return 0, fmt.Errorf("DownloadToFile failed: %v", err)
+ }
+ dst := filepath.Join(dir, "install")
+ if err := pkglib.Install(tmpFile, dst); err != nil {
+ return 0, fmt.Errorf("packages.Install failed: %v", err)
+ }
+ return packageSize(dst), nil
+}
+
+func (mni *mockDeviceInvoker) Install(call ipc.ServerContext, appName string, config device.Config, packages application.Packages) (string, error) {
+ is := InstallStimulus{"Install", appName, config, packages, application.Envelope{}, nil}
if appName != appNameNoFetch {
// Fetch the envelope and record it in the stimulus.
envelope, err := repository.ApplicationClient(appName).Match(call.Context(), []string{"test"})
@@ -156,22 +175,21 @@
// the file(s) that make up each package, and record that in the
// stimulus.
for pkgLocalName, pkgVON := range envelope.Packages {
- dir, err := ioutil.TempDir("", "package")
+ size, err := fetchPackageSize(call.Context(), pkgVON.File)
if err != nil {
- return "", fmt.Errorf("failed to create temp package dir: %v", err)
+ return "", err
}
- defer os.RemoveAll(dir)
- tmpFile := filepath.Join(dir, pkgLocalName)
- if err := binlib.DownloadToFile(call.Context(), pkgVON.File, tmpFile); err != nil {
- return "", fmt.Errorf("DownloadToFile failed: %v", err)
- }
- dst := filepath.Join(dir, "install")
- if err := packages.Install(tmpFile, dst); err != nil {
- return "", fmt.Errorf("packages.Install failed: %v", err)
- }
- is.files[naming.Join("packages", pkgLocalName)] = packageSize(dst)
+ is.files[naming.Join("packages", pkgLocalName)] = size
}
envelope.Packages = nil
+ for pkgLocalName, pkg := range packages {
+ size, err := fetchPackageSize(call.Context(), pkg.File)
+ if err != nil {
+ return "", err
+ }
+ is.files[naming.Join("overridepackages", pkgLocalName)] = size
+ }
+ is.packages = nil
is.envelope = envelope
}
r := mni.tape.Record(is).(InstallResponse)
diff --git a/tools/mgmt/device/impl/impl.go b/tools/mgmt/device/impl/impl.go
index 7e9a4a6..4f75fd4 100644
--- a/tools/mgmt/device/impl/impl.go
+++ b/tools/mgmt/device/impl/impl.go
@@ -8,6 +8,7 @@
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/security"
+ "v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/mgmt/device"
"v.io/lib/cmdline"
)
@@ -27,8 +28,24 @@
var configOverride configFlag = configFlag{}
+type packagesFlag application.Packages
+
+func (c *packagesFlag) String() string {
+ jsonPackages, _ := json.Marshal(c)
+ return string(jsonPackages)
+}
+func (c *packagesFlag) Set(s string) error {
+ if err := json.Unmarshal([]byte(s), c); err != nil {
+ return fmt.Errorf("Unmarshal(%v) failed: %v", s, err)
+ }
+ return nil
+}
+
+var packagesOverride packagesFlag = packagesFlag{}
+
func init() {
cmdInstall.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
+ cmdInstall.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"object name 1\"},\"pkg2\":{\"File\":\"object name 2\"}}'")
}
var cmdInstall = &cmdline.Command{
@@ -49,11 +66,12 @@
return cmd.UsageErrorf("install: incorrect number of arguments, expected %d, got %d", expected, got)
}
deviceName, appName := args[0], args[1]
- appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride))
+ appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), application.Packages(packagesOverride))
// Reset the value for any future invocations of "install" or
// "install-local" (we run more than one command per process in unit
// tests).
configOverride = configFlag{}
+ packagesOverride = packagesFlag{}
if err != nil {
return fmt.Errorf("Install failed: %v", err)
}
diff --git a/tools/mgmt/device/impl/impl_test.go b/tools/mgmt/device/impl/impl_test.go
index 47fa733..acfb7d9 100644
--- a/tools/mgmt/device/impl/impl_test.go
+++ b/tools/mgmt/device/impl/impl_test.go
@@ -9,6 +9,7 @@
"testing"
"v.io/core/veyron2/naming"
+ "v.io/core/veyron2/security"
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/mgmt/device"
"v.io/core/veyron2/verror"
@@ -182,9 +183,22 @@
deviceName := naming.JoinAddressName(endpoint.String(), "")
appId := "myBestAppID"
cfg := device.Config{"someflag": "somevalue"}
+ pkg := application.Packages{"pkg": application.PackageSpec{
+ File: "somename",
+ // If we leave this unset, the server will get a Signature that
+ // looks like the one we're setting below (in particular, the
+ // byte slices are empty instead of nil). This will cause
+ // reflect.DeepEqual to fail when we compare the packages object
+ // with what's set in the install stimulus.
+ //
+ // TODO(caprita/toddw): figure out why the server gets empty
+ // byte slices where we send nil ones.
+ Signature: security.Signature{Purpose: []uint8{}, Hash: "", R: []uint8{}, S: []uint8{}}},
+ }
for i, c := range []struct {
args []string
config device.Config
+ packages application.Packages
shouldErr bool
tapeResponse interface{}
expectedTape interface{}
@@ -192,6 +206,7 @@
{
[]string{"blech"},
nil,
+ nil,
true,
nil,
nil,
@@ -199,6 +214,7 @@
{
[]string{"blech1", "blech2", "blech3", "blech4"},
nil,
+ nil,
true,
nil,
nil,
@@ -206,6 +222,7 @@
{
[]string{deviceName, appNameNoFetch, "not-valid-json"},
nil,
+ nil,
true,
nil,
nil,
@@ -213,16 +230,18 @@
{
[]string{deviceName, appNameNoFetch},
nil,
+ nil,
false,
InstallResponse{appId, nil},
- InstallStimulus{"Install", appNameNoFetch, nil, application.Envelope{}, nil},
+ InstallStimulus{"Install", appNameNoFetch, nil, nil, application.Envelope{}, nil},
},
{
[]string{deviceName, appNameNoFetch},
cfg,
+ pkg,
false,
InstallResponse{appId, nil},
- InstallStimulus{"Install", appNameNoFetch, cfg, application.Envelope{}, nil},
+ InstallStimulus{"Install", appNameNoFetch, cfg, pkg, application.Envelope{}, nil},
},
} {
tape.SetResponses([]interface{}{c.tapeResponse})
@@ -233,6 +252,13 @@
}
c.args = append([]string{fmt.Sprintf("--config=%s", string(jsonConfig))}, c.args...)
}
+ if c.packages != nil {
+ jsonPackages, err := json.Marshal(c.packages)
+ if err != nil {
+ t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.packages, err)
+ }
+ c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
+ }
c.args = append([]string{"install"}, c.args...)
err := cmd.Execute(c.args)
if c.shouldErr {
@@ -250,7 +276,7 @@
t.Fatalf("test case %d: Unexpected output from Install. Got %q, expected %q", i, got, expected)
}
if got, expected := tape.Play(), []interface{}{c.expectedTape}; !reflect.DeepEqual(expected, got) {
- t.Errorf("test case %d: invalid call sequence. Got %v, want %v", i, got, expected)
+ t.Errorf("test case %d: invalid call sequence. Got %#v, want %#v", i, got, expected)
}
}
tape.Rewind()
diff --git a/tools/mgmt/device/impl/local_install.go b/tools/mgmt/device/impl/local_install.go
index dbdc6f5..21200c7 100644
--- a/tools/mgmt/device/impl/local_install.go
+++ b/tools/mgmt/device/impl/local_install.go
@@ -22,7 +22,7 @@
"v.io/core/veyron2/services/security/access"
"v.io/core/veyron2/uniqueid"
- "v.io/core/veyron/services/mgmt/lib/packages"
+ pkglib "v.io/core/veyron/services/mgmt/lib/packages"
"v.io/lib/cmdline"
)
@@ -44,6 +44,7 @@
func init() {
cmdInstallLocal.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
+ cmdInstallLocal.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"object name 1\"},\"pkg2\":{\"File\":\"object name 2\"}}'")
}
type openAuthorizer struct{}
@@ -170,7 +171,7 @@
}
h.Write(bytes)
part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
- return []binary.PartInfo{part}, packages.MediaInfoForFileName(fileName), nil
+ return []binary.PartInfo{part}, pkglib.MediaInfoForFileName(fileName), nil
}
func (binaryInvoker) Upload(repository.BinaryUploadContext, int32) error {
@@ -210,7 +211,7 @@
// Directory packages first get zip'ped.
if info.IsDir() {
fileName = filepath.Join(tmpZipDir, info.Name()+".zip")
- if err := packages.CreateZip(fileName, p); err != nil {
+ if err := pkglib.CreateZip(fileName, p); err != nil {
return "", "", err
}
}
@@ -293,15 +294,25 @@
}
envelope.Packages[pname] = application.PackageSpec{File: oname}
}
+ packagesRewritten := application.Packages{}
+ for pname, pspec := range packagesOverride {
+ _, oname, err := servePackage(pspec.File, server, tmpZipDir)
+ if err != nil {
+ return err
+ }
+ pspec.File = oname
+ packagesRewritten[pname] = pspec
+ }
appName, err := server.serve("application", repository.ApplicationServer(envelopeInvoker(envelope)))
if err != nil {
return err
}
- appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride))
+ appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), packagesRewritten)
// Reset the value for any future invocations of "install" or
// "install-local" (we run more than one command per process in unit
// tests).
configOverride = configFlag{}
+ packagesOverride = packagesFlag{}
if err != nil {
return fmt.Errorf("Install failed: %v", err)
}
diff --git a/tools/mgmt/device/impl/local_install_test.go b/tools/mgmt/device/impl/local_install_test.go
index 4c8b5b5..a28bce1 100644
--- a/tools/mgmt/device/impl/local_install_test.go
+++ b/tools/mgmt/device/impl/local_install_test.go
@@ -96,26 +96,45 @@
createFile(t, pkgFile1, "1234567")
pkgFile2 := filepath.Join(testPackagesDir, "file2")
createFile(t, pkgFile2, string([]byte{0x01, 0x02, 0x03, 0x04}))
- pkgDir := filepath.Join(testPackagesDir, "dir")
- if err := os.Mkdir(pkgDir, 0700); err != nil {
- t.Fatalf("Failed to create dir: %v", err)
+ pkgDir1 := filepath.Join(testPackagesDir, "dir1")
+ if err := os.Mkdir(pkgDir1, 0700); err != nil {
+ t.Fatalf("Failed to create dir1: %v", err)
}
- createFile(t, filepath.Join(pkgDir, "f1"), "123")
- createFile(t, filepath.Join(pkgDir, "f2"), "456")
- createFile(t, filepath.Join(pkgDir, "f3"), "7890")
+ createFile(t, filepath.Join(pkgDir1, "f1"), "123")
+ createFile(t, filepath.Join(pkgDir1, "f2"), "456")
+ createFile(t, filepath.Join(pkgDir1, "f3"), "7890")
+
+ pkgFile3 := filepath.Join(testPackagesDir, "file3")
+ createFile(t, pkgFile3, "12345")
+ pkgFile4 := filepath.Join(testPackagesDir, "file4")
+ createFile(t, pkgFile4, "123")
+ pkgDir2 := filepath.Join(testPackagesDir, "dir2")
+ if err := os.Mkdir(pkgDir2, 0700); err != nil {
+ t.Fatalf("Failed to create dir2: %v", err)
+ }
+ createFile(t, filepath.Join(pkgDir2, "f1"), "123456")
+ createFile(t, filepath.Join(pkgDir2, "f2"), "78")
+ pkg := application.Packages{
+ "overridepkg1": application.PackageSpec{File: pkgFile3},
+ "overridepkg2": application.PackageSpec{File: pkgFile4},
+ "overridepkg3": application.PackageSpec{File: pkgDir2},
+ }
for i, c := range []struct {
args []string
config device.Config
+ packages application.Packages
expectedTape interface{}
}{
{
[]string{deviceName, appTitle, binary},
nil,
+ nil,
InstallStimulus{
"Install",
appNameAfterFetch,
nil,
+ nil,
application.Envelope{
Title: appTitle,
Binary: binaryNameAfterFetch,
@@ -127,10 +146,12 @@
{
[]string{deviceName, appTitle, binary},
cfg,
+ nil,
InstallStimulus{
"Install",
appNameAfterFetch,
cfg,
+ nil,
application.Envelope{
Title: appTitle,
Binary: binaryNameAfterFetch,
@@ -142,10 +163,12 @@
{
[]string{deviceName, appTitle, "ENV1=V1", "ENV2=V2", binary, "FLAG1=V1", "FLAG2=V2"},
nil,
+ nil,
InstallStimulus{
"Install",
appNameAfterFetch,
nil,
+ nil,
application.Envelope{
Title: appTitle,
Binary: binaryNameAfterFetch,
@@ -157,11 +180,13 @@
map[string]int64{"binary": binarySize}},
},
{
- []string{deviceName, appTitle, "ENV=V", binary, "FLAG=V", "PACKAGES", pkgFile1, pkgFile2, pkgDir},
+ []string{deviceName, appTitle, "ENV=V", binary, "FLAG=V", "PACKAGES", pkgFile1, pkgFile2, pkgDir1},
nil,
+ pkg,
InstallStimulus{"Install",
appNameAfterFetch,
nil,
+ nil,
application.Envelope{
Title: appTitle,
Binary: binaryNameAfterFetch,
@@ -170,7 +195,16 @@
Env: []string{"ENV=V"},
Args: []string{"FLAG=V"},
},
- map[string]int64{"binary": binarySize, "packages/file1.txt": 7, "packages/file2": 4, "packages/dir": 10}},
+ map[string]int64{
+ "binary": binarySize,
+ "packages/file1.txt": 7,
+ "packages/file2": 4,
+ "packages/dir1": 10,
+ "overridepackages/overridepkg1": 5,
+ "overridepackages/overridepkg2": 3,
+ "overridepackages/overridepkg3": 8,
+ },
+ },
},
} {
const appId = "myBestAppID"
@@ -182,6 +216,13 @@
}
c.args = append([]string{fmt.Sprintf("--config=%s", string(jsonConfig))}, c.args...)
}
+ if c.packages != nil {
+ jsonPackages, err := json.Marshal(c.packages)
+ if err != nil {
+ t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.packages, err)
+ }
+ c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
+ }
c.args = append([]string{"install-local"}, c.args...)
if err := cmd.Execute(c.args); err != nil {
t.Fatalf("test case %d: %v", i, err)