blob: 946bbb71ba8650c98287a7ebf400e2cdb9f5761b [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
Jiri Simsa6ac95222015-02-23 16:11:49 -080013 "v.io/v23"
14 "v.io/v23/context"
15 "v.io/v23/ipc"
16 "v.io/v23/naming"
17 "v.io/v23/security"
18 "v.io/v23/services/mgmt/application"
19 "v.io/v23/services/mgmt/binary"
20 "v.io/v23/services/mgmt/device"
21 "v.io/v23/services/mgmt/repository"
22 "v.io/v23/services/security/access"
23 "v.io/v23/uniqueid"
24 "v.io/v23/vlog"
Bogdan Caprita50e21532015-02-06 17:33:27 -080025
Bogdan Caprita047fa452015-02-11 10:18:04 -080026 pkglib "v.io/core/veyron/services/mgmt/lib/packages"
Jiri Simsa24a71552015-02-27 11:31:36 -080027 "v.io/x/lib/cmdline"
Bogdan Capritab646a1c2015-01-30 19:16:37 -080028)
29
Bogdan Capritab646a1c2015-01-30 19:16:37 -080030var cmdInstallLocal = &cmdline.Command{
31 Run: runInstallLocal,
32 Name: "install-local",
33 Short: "Install the given application from the local system.",
34 Long: "Install the given application, specified using a local path.",
Bogdan Caprita50e21532015-02-06 17:33:27 -080035 ArgsName: "<device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]",
Bogdan Capritab646a1c2015-01-30 19:16:37 -080036 ArgsLong: `
37<device> is the veyron object name of the device manager's app service.
38
39<title> is the app title.
40
41This is followed by an arbitrary number of environment variable settings, the
Bogdan Caprita50e21532015-02-06 17:33:27 -080042local path for the binary to install, and arbitrary flag settings and args.
43Optionally, this can be followed by 'PACKAGES' and a list of local files and
44directories to be installed as packages for the app`}
Bogdan Capritab646a1c2015-01-30 19:16:37 -080045
Bogdan Caprita8964d3f2015-02-02 13:47:39 -080046func init() {
47 cmdInstallLocal.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
Bogdan Capritaf5b94412015-02-17 10:15:21 -080048 cmdInstallLocal.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"local file path1\"},\"pkg2\":{\"File\":\"local file path 2\"}}'")
Bogdan Caprita8964d3f2015-02-02 13:47:39 -080049}
50
Bogdan Capritab646a1c2015-01-30 19:16:37 -080051type openAuthorizer struct{}
52
53func (openAuthorizer) Authorize(security.Context) error { return nil }
54
55type mapDispatcher map[string]interface{}
56
57func (d mapDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
58 o, ok := d[suffix]
59 if !ok {
60 return nil, nil, fmt.Errorf("suffix %s not found", suffix)
61 }
62 // TODO(caprita): Do not open authorizer even for a short-lived server.
63 return o, &openAuthorizer{}, nil
64}
65
Bogdan Caprita20088dd2015-02-09 19:01:48 -080066type mapServer struct {
67 name string
68 dispatcher mapDispatcher
69}
70
71func (ms *mapServer) serve(name string, object interface{}) (string, error) {
72 if _, ok := ms.dispatcher[name]; ok {
73 return "", fmt.Errorf("can't have more than one object with name %v", name)
74 }
75 ms.dispatcher[name] = object
76 return naming.Join(ms.name, name), nil
77}
78
79func createServer(ctx *context.T, stderr io.Writer) (*mapServer, func(), error) {
Jiri Simsa6ac95222015-02-23 16:11:49 -080080 server, err := v23.NewServer(ctx)
Bogdan Capritab646a1c2015-01-30 19:16:37 -080081 if err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -080082 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080083 }
Jiri Simsa6ac95222015-02-23 16:11:49 -080084 spec := v23.GetListenSpec(ctx)
Bogdan Capritab646a1c2015-01-30 19:16:37 -080085 endpoints, err := server.Listen(spec)
86 if err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -080087 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080088 }
89 var name string
90 if spec.Proxy != "" {
91 id, err := uniqueid.Random()
92 if err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -080093 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -080094 }
95 name = id.String()
96 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -080097 dispatcher := make(mapDispatcher)
98 if err := server.ServeDispatcher(name, dispatcher); err != nil {
99 return nil, nil, err
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800100 }
Bogdan Caprita2250a3c2015-02-19 21:50:13 -0800101 vlog.VI(1).Infof("Server listening on %v (%v)", endpoints, name)
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800102 cleanup := func() {
103 if err := server.Stop(); err != nil {
104 fmt.Fprintf(stderr, "server.Stop failed: %v", err)
105 }
106 }
107 if name != "" {
108 // Send a name rooted in our namespace root rather than the
109 // relative name (in case the device manager uses a different
110 // namespace root).
111 //
112 // TODO(caprita): Avoid relying on a mounttable altogether, and
113 // instead pull out the proxied address and just send that.
Jiri Simsa6ac95222015-02-23 16:11:49 -0800114 nsRoots := v23.GetNamespace(ctx).Roots()
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800115 if len(nsRoots) > 0 {
116 name = naming.Join(nsRoots[0], name)
117 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800118 } else if len(endpoints) > 0 {
119 name = endpoints[0].Name()
120 } else {
121 return nil, nil, fmt.Errorf("no endpoints")
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800122 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800123 return &mapServer{name: name, dispatcher: dispatcher}, cleanup, nil
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800124}
125
126var errNotImplemented = fmt.Errorf("method not implemented")
127
128type binaryInvoker string
129
130func (binaryInvoker) Create(ipc.ServerContext, int32, repository.MediaInfo) error {
131 return errNotImplemented
132}
133
134func (binaryInvoker) Delete(ipc.ServerContext) error {
135 return errNotImplemented
136}
137
138func (i binaryInvoker) Download(ctx repository.BinaryDownloadContext, _ int32) error {
139 fileName := string(i)
140 file, err := os.Open(fileName)
141 if err != nil {
142 return err
143 }
144 defer file.Close()
145 bufferLength := 4096
146 buffer := make([]byte, bufferLength)
147 sender := ctx.SendStream()
148 for {
149 n, err := file.Read(buffer)
150 switch err {
151 case io.EOF:
152 return nil
153 case nil:
154 if err := sender.Send(buffer[:n]); err != nil {
155 return err
156 }
157 default:
158 return err
159 }
160 }
161}
162
163func (binaryInvoker) DownloadURL(ipc.ServerContext) (string, int64, error) {
164 return "", 0, errNotImplemented
165}
166
167func (i binaryInvoker) Stat(ctx ipc.ServerContext) ([]binary.PartInfo, repository.MediaInfo, error) {
168 fileName := string(i)
169 h := md5.New()
170 bytes, err := ioutil.ReadFile(fileName)
171 if err != nil {
172 return []binary.PartInfo{}, repository.MediaInfo{}, err
173 }
174 h.Write(bytes)
175 part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
Bogdan Caprita047fa452015-02-11 10:18:04 -0800176 return []binary.PartInfo{part}, pkglib.MediaInfoForFileName(fileName), nil
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800177}
178
179func (binaryInvoker) Upload(repository.BinaryUploadContext, int32) error {
180 return errNotImplemented
181}
182
183func (binaryInvoker) GetACL(ctx ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
184 return nil, "", errNotImplemented
185}
186
187func (binaryInvoker) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
188 return errNotImplemented
189}
190
191type envelopeInvoker application.Envelope
192
193func (i envelopeInvoker) Match(ipc.ServerContext, []string) (application.Envelope, error) {
194 return application.Envelope(i), nil
195}
196func (envelopeInvoker) GetACL(ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
197 return nil, "", errNotImplemented
198}
199
200func (envelopeInvoker) SetACL(ipc.ServerContext, access.TaggedACLMap, string) error {
201 return errNotImplemented
202}
203
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800204func servePackage(p string, ms *mapServer, tmpZipDir string) (string, string, error) {
205 info, err := os.Stat(p)
206 if os.IsNotExist(err) {
207 return "", "", fmt.Errorf("%v not found: %v", p, err)
208 } else if err != nil {
209 return "", "", fmt.Errorf("Stat(%v) failed: %v", p, err)
210 }
211 pkgName := naming.Join("packages", info.Name())
212 fileName := p
213 // Directory packages first get zip'ped.
214 if info.IsDir() {
215 fileName = filepath.Join(tmpZipDir, info.Name()+".zip")
Bogdan Caprita047fa452015-02-11 10:18:04 -0800216 if err := pkglib.CreateZip(fileName, p); err != nil {
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800217 return "", "", err
218 }
219 }
220 name, err := ms.serve(pkgName, repository.BinaryServer(binaryInvoker(fileName)))
221 return info.Name(), name, err
222}
223
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800224// runInstallLocal creates a new envelope on the fly from the provided
225// arguments, and then points the device manager back to itself for downloading
226// the app envelope and binary.
227//
228// It sets up an app and binary server that only lives for the duration of the
229// command, and listens on the profile's listen spec. The caller should set the
230// --veyron.proxy if the machine running the command is not accessible from the
231// device manager.
232//
233// TODO(caprita/ashankar): We should use bi-directional streams to get this
234// working over the same connection that the command makes to the device
235// manager.
236func runInstallLocal(cmd *cmdline.Command, args []string) error {
237 if expectedMin, got := 2, len(args); got < expectedMin {
238 return cmd.UsageErrorf("install-local: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
239 }
240 deviceName, title := args[0], args[1]
241 args = args[2:]
242 envelope := application.Envelope{Title: title}
243 // Extract the environment settings, binary, and arguments.
244 firstNonEnv := len(args)
245 for i, arg := range args {
246 if strings.Index(arg, "=") <= 0 {
247 firstNonEnv = i
248 break
249 }
250 }
251 envelope.Env = args[:firstNonEnv]
252 args = args[firstNonEnv:]
253 if len(args) == 0 {
254 return cmd.UsageErrorf("install-local: missing binary")
255 }
256 binary := args[0]
Bogdan Caprita50e21532015-02-06 17:33:27 -0800257 args = args[1:]
258 firstNonArg, firstPackage := len(args), len(args)
259 for i, arg := range args {
260 if arg == "PACKAGES" {
261 firstNonArg = i
262 firstPackage = i + 1
263 break
264 }
265 }
266 envelope.Args = args[:firstNonArg]
267 pkgs := args[firstPackage:]
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800268 if _, err := os.Stat(binary); err != nil {
269 return fmt.Errorf("binary %v not found: %v", binary, err)
270 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800271 server, cancel, err := createServer(gctx, cmd.Stderr())
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800272 if err != nil {
273 return fmt.Errorf("failed to create server: %v", err)
274 }
275 defer cancel()
Bogdan Capritac25a48c2015-02-12 13:45:51 -0800276 envelope.Binary.File, err = server.serve("binary", repository.BinaryServer(binaryInvoker(binary)))
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800277 if err != nil {
278 return err
279 }
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800280
Bogdan Caprita50e21532015-02-06 17:33:27 -0800281 // For each package dir/file specified in the arguments list, set up an
282 // object in the binary service to serve that package, and add the
283 // object name to the envelope's Packages map.
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800284 tmpZipDir, err := ioutil.TempDir("", "packages")
285 if err != nil {
286 return fmt.Errorf("failed to create a temp dir for zip packages: %v", err)
287 }
288 defer os.RemoveAll(tmpZipDir)
Bogdan Caprita50e21532015-02-06 17:33:27 -0800289 for _, p := range pkgs {
290 if envelope.Packages == nil {
Bogdan Capritac25a48c2015-02-12 13:45:51 -0800291 envelope.Packages = make(application.Packages)
Bogdan Caprita50e21532015-02-06 17:33:27 -0800292 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800293 pname, oname, err := servePackage(p, server, tmpZipDir)
294 if err != nil {
295 return err
Bogdan Caprita50e21532015-02-06 17:33:27 -0800296 }
Bogdan Caprita2250a3c2015-02-19 21:50:13 -0800297 vlog.VI(1).Infof("package %v serving as %v", pname, oname)
Bogdan Capritac25a48c2015-02-12 13:45:51 -0800298 envelope.Packages[pname] = application.SignedFile{File: oname}
Bogdan Caprita50e21532015-02-06 17:33:27 -0800299 }
Bogdan Caprita047fa452015-02-11 10:18:04 -0800300 packagesRewritten := application.Packages{}
301 for pname, pspec := range packagesOverride {
302 _, oname, err := servePackage(pspec.File, server, tmpZipDir)
303 if err != nil {
304 return err
305 }
Bogdan Caprita2250a3c2015-02-19 21:50:13 -0800306 vlog.VI(1).Infof("package %v serving as %v", pname, oname)
Bogdan Caprita047fa452015-02-11 10:18:04 -0800307 pspec.File = oname
308 packagesRewritten[pname] = pspec
309 }
Bogdan Caprita20088dd2015-02-09 19:01:48 -0800310 appName, err := server.serve("application", repository.ApplicationServer(envelopeInvoker(envelope)))
311 if err != nil {
312 return err
313 }
Bogdan Caprita2250a3c2015-02-19 21:50:13 -0800314 vlog.VI(1).Infof("application serving envelope as %v", appName)
Bogdan Caprita047fa452015-02-11 10:18:04 -0800315 appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), packagesRewritten)
Bogdan Caprita8964d3f2015-02-02 13:47:39 -0800316 // Reset the value for any future invocations of "install" or
317 // "install-local" (we run more than one command per process in unit
318 // tests).
319 configOverride = configFlag{}
Bogdan Caprita047fa452015-02-11 10:18:04 -0800320 packagesOverride = packagesFlag{}
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800321 if err != nil {
322 return fmt.Errorf("Install failed: %v", err)
323 }
324 fmt.Fprintf(cmd.Stdout(), "Successfully installed: %q\n", naming.Join(deviceName, appID))
325 return nil
326}