blob: 21200c7da69ed8bd75469100e38be1dbfa2d99e6 [file] [log] [blame]
Bogdan Capritab646a1c2015-01-30 19:16:37 -08001package impl
2
3import (
4 "crypto/md5"
5 "encoding/hex"
6 "fmt"
7 "io"
8 "io/ioutil"
9 "os"
Bogdan Caprita50e21532015-02-06 17:33:27 -080010 "path/filepath"
Bogdan Capritab646a1c2015-01-30 19:16:37 -080011 "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 Caprita50e21532015-02-06 17:33:27 -080024
Bogdan Caprita047fa452015-02-11 10:18:04 -080025 pkglib "v.io/core/veyron/services/mgmt/lib/packages"
Bogdan Capritab646a1c2015-01-30 19:16:37 -080026 "v.io/lib/cmdline"
27)
28
Bogdan Capritab646a1c2015-01-30 19:16:37 -080029var 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 Caprita50e21532015-02-06 17:33:27 -080034 ArgsName: "<device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]",
Bogdan Capritab646a1c2015-01-30 19:16:37 -080035 ArgsLong: `
36<device> is the veyron object name of the device manager's app service.
37
38<title> is the app title.
39
40This is followed by an arbitrary number of environment variable settings, the
Bogdan Caprita50e21532015-02-06 17:33:27 -080041local path for the binary to install, and arbitrary flag settings and args.
42Optionally, this can be followed by 'PACKAGES' and a list of local files and
43directories to be installed as packages for the app`}
Bogdan Capritab646a1c2015-01-30 19:16:37 -080044
Bogdan Caprita8964d3f2015-02-02 13:47:39 -080045func init() {
46 cmdInstallLocal.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
Bogdan Caprita047fa452015-02-11 10:18:04 -080047 cmdInstallLocal.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"object name 1\"},\"pkg2\":{\"File\":\"object name 2\"}}'")
Bogdan Caprita8964d3f2015-02-02 13:47:39 -080048}
49
Bogdan Capritab646a1c2015-01-30 19:16:37 -080050type openAuthorizer struct{}
51
52func (openAuthorizer) Authorize(security.Context) error { return nil }
53
54type mapDispatcher map[string]interface{}
55
56func (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 Caprita20088dd2015-02-09 19:01:48 -080065type mapServer struct {
66 name string
67 dispatcher mapDispatcher
68}
69
70func (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
78func createServer(ctx *context.T, stderr io.Writer) (*mapServer, func(), error) {
Bogdan Capritab646a1c2015-01-30 19:16:37 -080079 server, err := veyron2.NewServer(ctx)
80 if err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -080081 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080082 }
83 spec := veyron2.GetListenSpec(ctx)
84 endpoints, err := server.Listen(spec)
85 if err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -080086 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080087 }
88 var name string
89 if spec.Proxy != "" {
90 id, err := uniqueid.Random()
91 if err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -080092 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080093 }
94 name = id.String()
95 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -080096 dispatcher := make(mapDispatcher)
97 if err := server.ServeDispatcher(name, dispatcher); err != nil {
98 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080099 }
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 Caprita20088dd2015-02-09 19:01:48 -0800116 } else if len(endpoints) > 0 {
117 name = endpoints[0].Name()
118 } else {
119 return nil, nil, fmt.Errorf("no endpoints")
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800120 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800121 return &mapServer{name: name, dispatcher: dispatcher}, cleanup, nil
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800122}
123
124var errNotImplemented = fmt.Errorf("method not implemented")
125
126type binaryInvoker string
127
128func (binaryInvoker) Create(ipc.ServerContext, int32, repository.MediaInfo) error {
129 return errNotImplemented
130}
131
132func (binaryInvoker) Delete(ipc.ServerContext) error {
133 return errNotImplemented
134}
135
136func (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
161func (binaryInvoker) DownloadURL(ipc.ServerContext) (string, int64, error) {
162 return "", 0, errNotImplemented
163}
164
165func (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 Caprita047fa452015-02-11 10:18:04 -0800174 return []binary.PartInfo{part}, pkglib.MediaInfoForFileName(fileName), nil
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800175}
176
177func (binaryInvoker) Upload(repository.BinaryUploadContext, int32) error {
178 return errNotImplemented
179}
180
181func (binaryInvoker) GetACL(ctx ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
182 return nil, "", errNotImplemented
183}
184
185func (binaryInvoker) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
186 return errNotImplemented
187}
188
189type envelopeInvoker application.Envelope
190
191func (i envelopeInvoker) Match(ipc.ServerContext, []string) (application.Envelope, error) {
192 return application.Envelope(i), nil
193}
194func (envelopeInvoker) GetACL(ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
195 return nil, "", errNotImplemented
196}
197
198func (envelopeInvoker) SetACL(ipc.ServerContext, access.TaggedACLMap, string) error {
199 return errNotImplemented
200}
201
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800202func 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 Caprita047fa452015-02-11 10:18:04 -0800214 if err := pkglib.CreateZip(fileName, p); err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800215 return "", "", err
216 }
217 }
218 name, err := ms.serve(pkgName, repository.BinaryServer(binaryInvoker(fileName)))
219 return info.Name(), name, err
220}
221
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800222// 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.
234func 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 Caprita50e21532015-02-06 17:33:27 -0800255 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 Capritab646a1c2015-01-30 19:16:37 -0800266 if _, err := os.Stat(binary); err != nil {
267 return fmt.Errorf("binary %v not found: %v", binary, err)
268 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800269 server, cancel, err := createServer(gctx, cmd.Stderr())
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800270 if err != nil {
271 return fmt.Errorf("failed to create server: %v", err)
272 }
273 defer cancel()
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800274 envelope.Binary, err = server.serve("binary", repository.BinaryServer(binaryInvoker(binary)))
275 if err != nil {
276 return err
277 }
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800278
Bogdan Caprita50e21532015-02-06 17:33:27 -0800279 // 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 Caprita20088dd2015-02-09 19:01:48 -0800282 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 Caprita50e21532015-02-06 17:33:27 -0800287 for _, p := range pkgs {
288 if envelope.Packages == nil {
gauthamt3dbef0c2015-02-10 12:26:02 -0800289 envelope.Packages = make(map[string]application.PackageSpec)
Bogdan Caprita50e21532015-02-06 17:33:27 -0800290 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800291 pname, oname, err := servePackage(p, server, tmpZipDir)
292 if err != nil {
293 return err
Bogdan Caprita50e21532015-02-06 17:33:27 -0800294 }
gauthamt3dbef0c2015-02-10 12:26:02 -0800295 envelope.Packages[pname] = application.PackageSpec{File: oname}
Bogdan Caprita50e21532015-02-06 17:33:27 -0800296 }
Bogdan Caprita047fa452015-02-11 10:18:04 -0800297 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 Caprita20088dd2015-02-09 19:01:48 -0800306 appName, err := server.serve("application", repository.ApplicationServer(envelopeInvoker(envelope)))
307 if err != nil {
308 return err
309 }
Bogdan Caprita047fa452015-02-11 10:18:04 -0800310 appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), packagesRewritten)
Bogdan Caprita8964d3f2015-02-02 13:47:39 -0800311 // 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 Caprita047fa452015-02-11 10:18:04 -0800315 packagesOverride = packagesFlag{}
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800316 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}