Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 1 | package impl |
| 2 | |
| 3 | import ( |
| 4 | "crypto/md5" |
| 5 | "encoding/hex" |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "io/ioutil" |
| 9 | "os" |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 10 | "path/filepath" |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 11 | "strings" |
| 12 | |
| 13 | "v.io/core/veyron2" |
| 14 | "v.io/core/veyron2/context" |
| 15 | "v.io/core/veyron2/ipc" |
| 16 | "v.io/core/veyron2/naming" |
| 17 | "v.io/core/veyron2/security" |
| 18 | "v.io/core/veyron2/services/mgmt/application" |
| 19 | "v.io/core/veyron2/services/mgmt/binary" |
| 20 | "v.io/core/veyron2/services/mgmt/device" |
| 21 | "v.io/core/veyron2/services/mgmt/repository" |
| 22 | "v.io/core/veyron2/services/security/access" |
| 23 | "v.io/core/veyron2/uniqueid" |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 24 | |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 25 | pkglib "v.io/core/veyron/services/mgmt/lib/packages" |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 26 | "v.io/lib/cmdline" |
| 27 | ) |
| 28 | |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 29 | var cmdInstallLocal = &cmdline.Command{ |
| 30 | Run: runInstallLocal, |
| 31 | Name: "install-local", |
| 32 | Short: "Install the given application from the local system.", |
| 33 | Long: "Install the given application, specified using a local path.", |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 34 | ArgsName: "<device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]", |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 35 | ArgsLong: ` |
| 36 | <device> is the veyron object name of the device manager's app service. |
| 37 | |
| 38 | <title> is the app title. |
| 39 | |
| 40 | This is followed by an arbitrary number of environment variable settings, the |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 41 | local path for the binary to install, and arbitrary flag settings and args. |
| 42 | Optionally, this can be followed by 'PACKAGES' and a list of local files and |
| 43 | directories to be installed as packages for the app`} |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 44 | |
Bogdan Caprita | 8964d3f | 2015-02-02 13:47:39 -0800 | [diff] [blame] | 45 | func init() { |
| 46 | cmdInstallLocal.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'") |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 47 | cmdInstallLocal.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"object name 1\"},\"pkg2\":{\"File\":\"object name 2\"}}'") |
Bogdan Caprita | 8964d3f | 2015-02-02 13:47:39 -0800 | [diff] [blame] | 48 | } |
| 49 | |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 50 | type openAuthorizer struct{} |
| 51 | |
| 52 | func (openAuthorizer) Authorize(security.Context) error { return nil } |
| 53 | |
| 54 | type mapDispatcher map[string]interface{} |
| 55 | |
| 56 | func (d mapDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) { |
| 57 | o, ok := d[suffix] |
| 58 | if !ok { |
| 59 | return nil, nil, fmt.Errorf("suffix %s not found", suffix) |
| 60 | } |
| 61 | // TODO(caprita): Do not open authorizer even for a short-lived server. |
| 62 | return o, &openAuthorizer{}, nil |
| 63 | } |
| 64 | |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 65 | type mapServer struct { |
| 66 | name string |
| 67 | dispatcher mapDispatcher |
| 68 | } |
| 69 | |
| 70 | func (ms *mapServer) serve(name string, object interface{}) (string, error) { |
| 71 | if _, ok := ms.dispatcher[name]; ok { |
| 72 | return "", fmt.Errorf("can't have more than one object with name %v", name) |
| 73 | } |
| 74 | ms.dispatcher[name] = object |
| 75 | return naming.Join(ms.name, name), nil |
| 76 | } |
| 77 | |
| 78 | func createServer(ctx *context.T, stderr io.Writer) (*mapServer, func(), error) { |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 79 | server, err := veyron2.NewServer(ctx) |
| 80 | if err != nil { |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 81 | return nil, nil, err |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 82 | } |
| 83 | spec := veyron2.GetListenSpec(ctx) |
| 84 | endpoints, err := server.Listen(spec) |
| 85 | if err != nil { |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 86 | return nil, nil, err |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 87 | } |
| 88 | var name string |
| 89 | if spec.Proxy != "" { |
| 90 | id, err := uniqueid.Random() |
| 91 | if err != nil { |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 92 | return nil, nil, err |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 93 | } |
| 94 | name = id.String() |
| 95 | } |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 96 | dispatcher := make(mapDispatcher) |
| 97 | if err := server.ServeDispatcher(name, dispatcher); err != nil { |
| 98 | return nil, nil, err |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 99 | } |
| 100 | cleanup := func() { |
| 101 | if err := server.Stop(); err != nil { |
| 102 | fmt.Fprintf(stderr, "server.Stop failed: %v", err) |
| 103 | } |
| 104 | } |
| 105 | if name != "" { |
| 106 | // Send a name rooted in our namespace root rather than the |
| 107 | // relative name (in case the device manager uses a different |
| 108 | // namespace root). |
| 109 | // |
| 110 | // TODO(caprita): Avoid relying on a mounttable altogether, and |
| 111 | // instead pull out the proxied address and just send that. |
| 112 | nsRoots := veyron2.GetNamespace(ctx).Roots() |
| 113 | if len(nsRoots) > 0 { |
| 114 | name = naming.Join(nsRoots[0], name) |
| 115 | } |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 116 | } else if len(endpoints) > 0 { |
| 117 | name = endpoints[0].Name() |
| 118 | } else { |
| 119 | return nil, nil, fmt.Errorf("no endpoints") |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 120 | } |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 121 | return &mapServer{name: name, dispatcher: dispatcher}, cleanup, nil |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | var errNotImplemented = fmt.Errorf("method not implemented") |
| 125 | |
| 126 | type binaryInvoker string |
| 127 | |
| 128 | func (binaryInvoker) Create(ipc.ServerContext, int32, repository.MediaInfo) error { |
| 129 | return errNotImplemented |
| 130 | } |
| 131 | |
| 132 | func (binaryInvoker) Delete(ipc.ServerContext) error { |
| 133 | return errNotImplemented |
| 134 | } |
| 135 | |
| 136 | func (i binaryInvoker) Download(ctx repository.BinaryDownloadContext, _ int32) error { |
| 137 | fileName := string(i) |
| 138 | file, err := os.Open(fileName) |
| 139 | if err != nil { |
| 140 | return err |
| 141 | } |
| 142 | defer file.Close() |
| 143 | bufferLength := 4096 |
| 144 | buffer := make([]byte, bufferLength) |
| 145 | sender := ctx.SendStream() |
| 146 | for { |
| 147 | n, err := file.Read(buffer) |
| 148 | switch err { |
| 149 | case io.EOF: |
| 150 | return nil |
| 151 | case nil: |
| 152 | if err := sender.Send(buffer[:n]); err != nil { |
| 153 | return err |
| 154 | } |
| 155 | default: |
| 156 | return err |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | func (binaryInvoker) DownloadURL(ipc.ServerContext) (string, int64, error) { |
| 162 | return "", 0, errNotImplemented |
| 163 | } |
| 164 | |
| 165 | func (i binaryInvoker) Stat(ctx ipc.ServerContext) ([]binary.PartInfo, repository.MediaInfo, error) { |
| 166 | fileName := string(i) |
| 167 | h := md5.New() |
| 168 | bytes, err := ioutil.ReadFile(fileName) |
| 169 | if err != nil { |
| 170 | return []binary.PartInfo{}, repository.MediaInfo{}, err |
| 171 | } |
| 172 | h.Write(bytes) |
| 173 | part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))} |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 174 | return []binary.PartInfo{part}, pkglib.MediaInfoForFileName(fileName), nil |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | func (binaryInvoker) Upload(repository.BinaryUploadContext, int32) error { |
| 178 | return errNotImplemented |
| 179 | } |
| 180 | |
| 181 | func (binaryInvoker) GetACL(ctx ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) { |
| 182 | return nil, "", errNotImplemented |
| 183 | } |
| 184 | |
| 185 | func (binaryInvoker) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error { |
| 186 | return errNotImplemented |
| 187 | } |
| 188 | |
| 189 | type envelopeInvoker application.Envelope |
| 190 | |
| 191 | func (i envelopeInvoker) Match(ipc.ServerContext, []string) (application.Envelope, error) { |
| 192 | return application.Envelope(i), nil |
| 193 | } |
| 194 | func (envelopeInvoker) GetACL(ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) { |
| 195 | return nil, "", errNotImplemented |
| 196 | } |
| 197 | |
| 198 | func (envelopeInvoker) SetACL(ipc.ServerContext, access.TaggedACLMap, string) error { |
| 199 | return errNotImplemented |
| 200 | } |
| 201 | |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 202 | func servePackage(p string, ms *mapServer, tmpZipDir string) (string, string, error) { |
| 203 | info, err := os.Stat(p) |
| 204 | if os.IsNotExist(err) { |
| 205 | return "", "", fmt.Errorf("%v not found: %v", p, err) |
| 206 | } else if err != nil { |
| 207 | return "", "", fmt.Errorf("Stat(%v) failed: %v", p, err) |
| 208 | } |
| 209 | pkgName := naming.Join("packages", info.Name()) |
| 210 | fileName := p |
| 211 | // Directory packages first get zip'ped. |
| 212 | if info.IsDir() { |
| 213 | fileName = filepath.Join(tmpZipDir, info.Name()+".zip") |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 214 | if err := pkglib.CreateZip(fileName, p); err != nil { |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 215 | return "", "", err |
| 216 | } |
| 217 | } |
| 218 | name, err := ms.serve(pkgName, repository.BinaryServer(binaryInvoker(fileName))) |
| 219 | return info.Name(), name, err |
| 220 | } |
| 221 | |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 222 | // runInstallLocal creates a new envelope on the fly from the provided |
| 223 | // arguments, and then points the device manager back to itself for downloading |
| 224 | // the app envelope and binary. |
| 225 | // |
| 226 | // It sets up an app and binary server that only lives for the duration of the |
| 227 | // command, and listens on the profile's listen spec. The caller should set the |
| 228 | // --veyron.proxy if the machine running the command is not accessible from the |
| 229 | // device manager. |
| 230 | // |
| 231 | // TODO(caprita/ashankar): We should use bi-directional streams to get this |
| 232 | // working over the same connection that the command makes to the device |
| 233 | // manager. |
| 234 | func runInstallLocal(cmd *cmdline.Command, args []string) error { |
| 235 | if expectedMin, got := 2, len(args); got < expectedMin { |
| 236 | return cmd.UsageErrorf("install-local: incorrect number of arguments, expected at least %d, got %d", expectedMin, got) |
| 237 | } |
| 238 | deviceName, title := args[0], args[1] |
| 239 | args = args[2:] |
| 240 | envelope := application.Envelope{Title: title} |
| 241 | // Extract the environment settings, binary, and arguments. |
| 242 | firstNonEnv := len(args) |
| 243 | for i, arg := range args { |
| 244 | if strings.Index(arg, "=") <= 0 { |
| 245 | firstNonEnv = i |
| 246 | break |
| 247 | } |
| 248 | } |
| 249 | envelope.Env = args[:firstNonEnv] |
| 250 | args = args[firstNonEnv:] |
| 251 | if len(args) == 0 { |
| 252 | return cmd.UsageErrorf("install-local: missing binary") |
| 253 | } |
| 254 | binary := args[0] |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 255 | args = args[1:] |
| 256 | firstNonArg, firstPackage := len(args), len(args) |
| 257 | for i, arg := range args { |
| 258 | if arg == "PACKAGES" { |
| 259 | firstNonArg = i |
| 260 | firstPackage = i + 1 |
| 261 | break |
| 262 | } |
| 263 | } |
| 264 | envelope.Args = args[:firstNonArg] |
| 265 | pkgs := args[firstPackage:] |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 266 | if _, err := os.Stat(binary); err != nil { |
| 267 | return fmt.Errorf("binary %v not found: %v", binary, err) |
| 268 | } |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 269 | server, cancel, err := createServer(gctx, cmd.Stderr()) |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 270 | if err != nil { |
| 271 | return fmt.Errorf("failed to create server: %v", err) |
| 272 | } |
| 273 | defer cancel() |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 274 | envelope.Binary, err = server.serve("binary", repository.BinaryServer(binaryInvoker(binary))) |
| 275 | if err != nil { |
| 276 | return err |
| 277 | } |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 278 | |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 279 | // For each package dir/file specified in the arguments list, set up an |
| 280 | // object in the binary service to serve that package, and add the |
| 281 | // object name to the envelope's Packages map. |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 282 | tmpZipDir, err := ioutil.TempDir("", "packages") |
| 283 | if err != nil { |
| 284 | return fmt.Errorf("failed to create a temp dir for zip packages: %v", err) |
| 285 | } |
| 286 | defer os.RemoveAll(tmpZipDir) |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 287 | for _, p := range pkgs { |
| 288 | if envelope.Packages == nil { |
gauthamt | 3dbef0c | 2015-02-10 12:26:02 -0800 | [diff] [blame] | 289 | envelope.Packages = make(map[string]application.PackageSpec) |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 290 | } |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 291 | pname, oname, err := servePackage(p, server, tmpZipDir) |
| 292 | if err != nil { |
| 293 | return err |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 294 | } |
gauthamt | 3dbef0c | 2015-02-10 12:26:02 -0800 | [diff] [blame] | 295 | envelope.Packages[pname] = application.PackageSpec{File: oname} |
Bogdan Caprita | 50e2153 | 2015-02-06 17:33:27 -0800 | [diff] [blame] | 296 | } |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 297 | packagesRewritten := application.Packages{} |
| 298 | for pname, pspec := range packagesOverride { |
| 299 | _, oname, err := servePackage(pspec.File, server, tmpZipDir) |
| 300 | if err != nil { |
| 301 | return err |
| 302 | } |
| 303 | pspec.File = oname |
| 304 | packagesRewritten[pname] = pspec |
| 305 | } |
Bogdan Caprita | 20088dd | 2015-02-09 19:01:48 -0800 | [diff] [blame] | 306 | appName, err := server.serve("application", repository.ApplicationServer(envelopeInvoker(envelope))) |
| 307 | if err != nil { |
| 308 | return err |
| 309 | } |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 310 | appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), packagesRewritten) |
Bogdan Caprita | 8964d3f | 2015-02-02 13:47:39 -0800 | [diff] [blame] | 311 | // Reset the value for any future invocations of "install" or |
| 312 | // "install-local" (we run more than one command per process in unit |
| 313 | // tests). |
| 314 | configOverride = configFlag{} |
Bogdan Caprita | 047fa45 | 2015-02-11 10:18:04 -0800 | [diff] [blame^] | 315 | packagesOverride = packagesFlag{} |
Bogdan Caprita | b646a1c | 2015-01-30 19:16:37 -0800 | [diff] [blame] | 316 | if err != nil { |
| 317 | return fmt.Errorf("Install failed: %v", err) |
| 318 | } |
| 319 | fmt.Fprintf(cmd.Stdout(), "Successfully installed: %q\n", naming.Join(deviceName, appID)) |
| 320 | return nil |
| 321 | } |