cmd/mgmt/device/impl: add publish command for uploading to binaryd/applicationd
Publish is meant as a one-stop-shop to take a local binary and create a binary
service entry and an envelope for it. It can handle several binaries at a time
for 'batch' publishing.
Change-Id: I4a40fccd8a625dd254682d552cb9ae9b5f833d62
diff --git a/cmd/mgmt/device/doc.go b/cmd/mgmt/device/doc.go
index 0ffed76..f31f925 100644
--- a/cmd/mgmt/device/doc.go
+++ b/cmd/mgmt/device/doc.go
@@ -23,6 +23,7 @@
update Update the device manager or application
debug Debug the device.
acl Tool for setting device manager ACLs
+ publish Publish the given application(s).
help Display help for commands or topics
Run "device help [command]" for command usage.
@@ -313,6 +314,30 @@
Instead of making the ACLs additive, do a complete replacement based on the
specified settings.
+Device Publish
+
+Publishes the given application(s) to the binary and application servers. The
+binaries should be in $VANADIUM_ROOT/release/go/bin/[<GOOS>_<GOARCH>]. The
+binary is published as <binserv>/<binary name>/<GOOS>-<GOARCH>/<TIMESTAMP>. The
+application envelope is published as <appserv>/<binary name>/0. Optionally, adds
+blessing patterns to the Read and Resolve ACLs.
+
+Usage:
+ device publish [flags] <binary name> ...
+
+The device publish flags are:
+ -appserv=applicationd
+ Name of application service.
+ -binserv=binaryd
+ Name of binary service.
+ -goarch=amd64
+ GOARCH for application.
+ -goos=linux
+ GOOS for application.
+ -readers=dev.v.io
+ If non-empty, comma-separated blessing patterns to add to Read and Resolve
+ ACL.
+
Device Help
Help with no args displays the usage of the parent command.
diff --git a/cmd/mgmt/device/impl/publish.go b/cmd/mgmt/device/impl/publish.go
new file mode 100644
index 0000000..35621e4
--- /dev/null
+++ b/cmd/mgmt/device/impl/publish.go
@@ -0,0 +1,161 @@
+package impl
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "v.io/v23/naming"
+ "v.io/v23/security"
+ "v.io/v23/services/mgmt/application"
+ "v.io/v23/services/security/access"
+ "v.io/v23/services/security/access/object"
+ "v.io/v23/verror"
+
+ "v.io/x/lib/cmdline"
+ appdimpl "v.io/x/ref/services/mgmt/application/impl"
+ "v.io/x/ref/services/mgmt/lib/binary"
+ irepos "v.io/x/ref/services/mgmt/repository"
+)
+
+// TODO(caprita): Add unit test.
+
+// TODO(caprita): Extend to include env, args, packages.
+
+var cmdPublish = &cmdline.Command{
+ Run: runPublish,
+ Name: "publish",
+ Short: "Publish the given application(s).",
+ Long: `
+Publishes the given application(s) to the binary and application servers.
+The binaries should be in $VANADIUM_ROOT/release/go/bin/[<GOOS>_<GOARCH>].
+The binary is published as <binserv>/<binary name>/<GOOS>-<GOARCH>/<TIMESTAMP>.
+The application envelope is published as <appserv>/<binary name>/0.
+Optionally, adds blessing patterns to the Read and Resolve ACLs.`,
+ ArgsName: "<binary name> ...",
+}
+
+var binaryService, applicationService, goos, goarch, readBlessings string
+
+func init() {
+ cmdPublish.Flags.StringVar(&binaryService, "binserv", "binaryd", "Name of binary service.")
+ cmdPublish.Flags.StringVar(&applicationService, "appserv", "applicationd", "Name of application service.")
+ cmdPublish.Flags.StringVar(&goos, "goos", runtime.GOOS, "GOOS for application.")
+ cmdPublish.Flags.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH for application.")
+ cmdPublish.Flags.StringVar(&readBlessings, "readers", "dev.v.io", "If non-empty, comma-separated blessing patterns to add to Read and Resolve ACL.")
+}
+
+func setACLs(cmd *cmdline.Command, von string) error {
+ if readBlessings == "" {
+ return nil
+ }
+ acl, etag, err := object.ObjectClient(von).GetACL(gctx)
+ if err != nil {
+ // TODO(caprita): This is a workaround until we sort out the
+ // default ACLs for applicationd (see issue #1317). At that
+ // time, uncomment the line below.
+ //
+ // return err
+ acl = make(access.TaggedACLMap)
+ }
+ for _, blessing := range strings.Split(readBlessings, ",") {
+ for _, tag := range []access.Tag{access.Read, access.Resolve} {
+ acl.Add(security.BlessingPattern(blessing), string(tag))
+ }
+ }
+ if err := object.ObjectClient(von).SetACL(gctx, acl, etag); err != nil {
+ return err
+ }
+ fmt.Fprintf(cmd.Stdout(), "Added patterns %q to Read,Resolve ACL for %q\n", readBlessings, von)
+ return nil
+}
+
+func publishOne(cmd *cmdline.Command, binPath, binaryName string) error {
+ // Step 1, upload the binary to the binary service.
+
+ // TODO(caprita): Instead of the current timestamp, use each binary's
+ // BuildTimestamp from the buildinfo.
+ timestamp := time.Now().UTC().Format(time.RFC3339)
+ binaryVON := naming.Join(binaryService, binaryName, fmt.Sprintf("%s-%s", goos, goarch), timestamp)
+ binaryFile := filepath.Join(binPath, binaryName)
+ // TODO(caprita): Take signature of binary and put it in the envelope.
+ if _, err := binary.UploadFromFile(gctx, binaryVON, binaryFile); err != nil {
+ return err
+ }
+ fmt.Fprintf(cmd.Stdout(), "Binary %q uploaded from file %s\n", binaryVON, binaryFile)
+
+ // Step 2, set the acls for the uploaded binary.
+
+ if err := setACLs(cmd, binaryVON); err != nil {
+ return err
+ }
+
+ // Step 3, download existing envelope (or create a new one), update, and
+ // upload to application service.
+
+ // TODO(caprita): use the profile detection machinery and/or let user
+ // specify profiles by hand.
+ profiles := []string{fmt.Sprintf("%s-%s", goos, goarch)}
+ // TODO(caprita): use a label e.g. "prod" instead of "0".
+ appVON := naming.Join(applicationService, binaryName, "0")
+ appClient := irepos.ApplicationClient(appVON)
+ // NOTE: If profiles contains more than one entry, this will return only
+ // the first match. But presumably that's ok, since we're going to set
+ // the envelopes for all the profiles to the same envelope anyway below.
+ envelope, err := appClient.Match(gctx, profiles)
+ if verror.Is(err, appdimpl.ErrNotFound.ID) {
+ // There was nothing published yet, create a new envelope.
+ envelope = application.Envelope{Title: binaryName}
+ } else if err != nil {
+ return err
+ }
+ envelope.Binary.File = binaryVON
+ if err := irepos.ApplicationClient(appVON).Put(gctx, profiles, envelope); err != nil {
+ return err
+ }
+ fmt.Fprintf(cmd.Stdout(), "Published %q\n", appVON)
+
+ // Step 4, set the acls for the uploaded envelope.
+
+ if err := setACLs(cmd, appVON); err != nil {
+ return err
+ }
+ return nil
+}
+
+func runPublish(cmd *cmdline.Command, args []string) error {
+ if expectedMin, got := 1, len(args); got < expectedMin {
+ return cmd.UsageErrorf("publish: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
+ }
+ binaries := args
+ vroot := os.Getenv("VANADIUM_ROOT")
+ if vroot == "" {
+ return cmd.UsageErrorf("publish: $VANADIUM_ROOT environment variable should be set")
+ }
+ binPath := filepath.Join(vroot, "release/go/bin")
+ if goos != runtime.GOOS || goarch != runtime.GOARCH {
+ binPath = filepath.Join(binPath, fmt.Sprintf("%s_%s", goos, goarch))
+ }
+ if fi, err := os.Stat(binPath); err != nil {
+ return cmd.UsageErrorf("publish: failed to stat %v: %v", binPath, err)
+ } else if !fi.IsDir() {
+ return cmd.UsageErrorf("publish: %v is not a directory", binPath)
+ }
+ if binaryService == "" {
+ return cmd.UsageErrorf("publish: --binserv must point to a binary service name")
+ }
+ if applicationService == "" {
+ return cmd.UsageErrorf("publish: --appserv must point to an application service name")
+ }
+ var lastErr error
+ for _, b := range binaries {
+ if err := publishOne(cmd, binPath, b); err != nil {
+ fmt.Fprintf(cmd.Stderr(), "Failed to publish %q: %v\n", b, err)
+ lastErr = err
+ }
+ }
+ return lastErr
+}
diff --git a/cmd/mgmt/device/impl/root.go b/cmd/mgmt/device/impl/root.go
index a05baaa..e555286 100644
--- a/cmd/mgmt/device/impl/root.go
+++ b/cmd/mgmt/device/impl/root.go
@@ -19,6 +19,6 @@
Long: `
The device tool facilitates interaction with the veyron device manager.
`,
- Children: []*cmdline.Command{cmdInstall, cmdInstallLocal, cmdUninstall, cmdStart, associateRoot(), cmdDescribe, cmdClaim, cmdStop, cmdSuspend, cmdResume, cmdRevert, cmdUpdate, cmdDebug, aclRoot()},
+ Children: []*cmdline.Command{cmdInstall, cmdInstallLocal, cmdUninstall, cmdStart, associateRoot(), cmdDescribe, cmdClaim, cmdStop, cmdSuspend, cmdResume, cmdRevert, cmdUpdate, cmdDebug, aclRoot(), cmdPublish},
}
}