blob: b6a926d6333c75eab6cfac3ed188db8b4d913c3f [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
25 "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\"}'")
47}
48
Bogdan Capritab646a1c2015-01-30 19:16:37 -080049type openAuthorizer struct{}
50
51func (openAuthorizer) Authorize(security.Context) error { return nil }
52
53type mapDispatcher map[string]interface{}
54
55func (d mapDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
56 o, ok := d[suffix]
57 if !ok {
58 return nil, nil, fmt.Errorf("suffix %s not found", suffix)
59 }
60 // TODO(caprita): Do not open authorizer even for a short-lived server.
61 return o, &openAuthorizer{}, nil
62}
63
64func createServer(ctx *context.T, stderr io.Writer, objects map[string]interface{}) (string, func(), error) {
65 server, err := veyron2.NewServer(ctx)
66 if err != nil {
67 return "", nil, err
68 }
69 spec := veyron2.GetListenSpec(ctx)
70 endpoints, err := server.Listen(spec)
71 if err != nil {
72 return "", nil, err
73 }
74 var name string
75 if spec.Proxy != "" {
76 id, err := uniqueid.Random()
77 if err != nil {
78 return "", nil, err
79 }
80 name = id.String()
81 }
82 if err := server.ServeDispatcher(name, mapDispatcher(objects)); err != nil {
83 return "", nil, err
84 }
85 cleanup := func() {
86 if err := server.Stop(); err != nil {
87 fmt.Fprintf(stderr, "server.Stop failed: %v", err)
88 }
89 }
90 if name != "" {
91 // Send a name rooted in our namespace root rather than the
92 // relative name (in case the device manager uses a different
93 // namespace root).
94 //
95 // TODO(caprita): Avoid relying on a mounttable altogether, and
96 // instead pull out the proxied address and just send that.
97 nsRoots := veyron2.GetNamespace(ctx).Roots()
98 if len(nsRoots) > 0 {
99 name = naming.Join(nsRoots[0], name)
100 }
101 return name, cleanup, nil
102 }
103 if len(endpoints) == 0 {
104 return "", nil, fmt.Errorf("no endpoints")
105 }
106 return endpoints[0].Name(), cleanup, nil
107}
108
109var errNotImplemented = fmt.Errorf("method not implemented")
110
111type binaryInvoker string
112
113func (binaryInvoker) Create(ipc.ServerContext, int32, repository.MediaInfo) error {
114 return errNotImplemented
115}
116
117func (binaryInvoker) Delete(ipc.ServerContext) error {
118 return errNotImplemented
119}
120
121func (i binaryInvoker) Download(ctx repository.BinaryDownloadContext, _ int32) error {
122 fileName := string(i)
123 file, err := os.Open(fileName)
124 if err != nil {
125 return err
126 }
127 defer file.Close()
128 bufferLength := 4096
129 buffer := make([]byte, bufferLength)
130 sender := ctx.SendStream()
131 for {
132 n, err := file.Read(buffer)
133 switch err {
134 case io.EOF:
135 return nil
136 case nil:
137 if err := sender.Send(buffer[:n]); err != nil {
138 return err
139 }
140 default:
141 return err
142 }
143 }
144}
145
146func (binaryInvoker) DownloadURL(ipc.ServerContext) (string, int64, error) {
147 return "", 0, errNotImplemented
148}
149
150func (i binaryInvoker) Stat(ctx ipc.ServerContext) ([]binary.PartInfo, repository.MediaInfo, error) {
151 fileName := string(i)
152 h := md5.New()
153 bytes, err := ioutil.ReadFile(fileName)
154 if err != nil {
155 return []binary.PartInfo{}, repository.MediaInfo{}, err
156 }
157 h.Write(bytes)
158 part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
Bogdan Caprita50e21532015-02-06 17:33:27 -0800159 return []binary.PartInfo{part}, packages.MediaInfoForFileName(fileName), nil
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800160}
161
162func (binaryInvoker) Upload(repository.BinaryUploadContext, int32) error {
163 return errNotImplemented
164}
165
166func (binaryInvoker) GetACL(ctx ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
167 return nil, "", errNotImplemented
168}
169
170func (binaryInvoker) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
171 return errNotImplemented
172}
173
174type envelopeInvoker application.Envelope
175
176func (i envelopeInvoker) Match(ipc.ServerContext, []string) (application.Envelope, error) {
177 return application.Envelope(i), nil
178}
179func (envelopeInvoker) GetACL(ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
180 return nil, "", errNotImplemented
181}
182
183func (envelopeInvoker) SetACL(ipc.ServerContext, access.TaggedACLMap, string) error {
184 return errNotImplemented
185}
186
187// runInstallLocal creates a new envelope on the fly from the provided
188// arguments, and then points the device manager back to itself for downloading
189// the app envelope and binary.
190//
191// It sets up an app and binary server that only lives for the duration of the
192// command, and listens on the profile's listen spec. The caller should set the
193// --veyron.proxy if the machine running the command is not accessible from the
194// device manager.
195//
196// TODO(caprita/ashankar): We should use bi-directional streams to get this
197// working over the same connection that the command makes to the device
198// manager.
199func runInstallLocal(cmd *cmdline.Command, args []string) error {
200 if expectedMin, got := 2, len(args); got < expectedMin {
201 return cmd.UsageErrorf("install-local: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
202 }
203 deviceName, title := args[0], args[1]
204 args = args[2:]
205 envelope := application.Envelope{Title: title}
206 // Extract the environment settings, binary, and arguments.
207 firstNonEnv := len(args)
208 for i, arg := range args {
209 if strings.Index(arg, "=") <= 0 {
210 firstNonEnv = i
211 break
212 }
213 }
214 envelope.Env = args[:firstNonEnv]
215 args = args[firstNonEnv:]
216 if len(args) == 0 {
217 return cmd.UsageErrorf("install-local: missing binary")
218 }
219 binary := args[0]
Bogdan Caprita50e21532015-02-06 17:33:27 -0800220 args = args[1:]
221 firstNonArg, firstPackage := len(args), len(args)
222 for i, arg := range args {
223 if arg == "PACKAGES" {
224 firstNonArg = i
225 firstPackage = i + 1
226 break
227 }
228 }
229 envelope.Args = args[:firstNonArg]
230 pkgs := args[firstPackage:]
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800231 if _, err := os.Stat(binary); err != nil {
232 return fmt.Errorf("binary %v not found: %v", binary, err)
233 }
234 objects := map[string]interface{}{"binary": repository.BinaryServer(binaryInvoker(binary))}
235 name, cancel, err := createServer(gctx, cmd.Stderr(), objects)
236 if err != nil {
237 return fmt.Errorf("failed to create server: %v", err)
238 }
239 defer cancel()
240 envelope.Binary = naming.Join(name, "binary")
241
Bogdan Caprita50e21532015-02-06 17:33:27 -0800242 // For each package dir/file specified in the arguments list, set up an
243 // object in the binary service to serve that package, and add the
244 // object name to the envelope's Packages map.
245 var tmpZipDir string
246 for _, p := range pkgs {
247 if envelope.Packages == nil {
248 envelope.Packages = make(map[string]string)
249 }
250 info, err := os.Stat(p)
251 if os.IsNotExist(err) {
252 return fmt.Errorf("%v not found: %v", p, err)
253 } else if err != nil {
254 return fmt.Errorf("Stat(%v) failed: %v", p, err)
255 }
256 pkgName := naming.Join("packages", info.Name())
257 if _, ok := objects[pkgName]; ok {
258 return fmt.Errorf("can't have more than one package with name %v", info.Name())
259 }
260 fileName := p
261 // Directory packages first get zip'ped.
262 if info.IsDir() {
263 if tmpZipDir == "" {
264 tmpZipDir, err = ioutil.TempDir("", "packages")
265 if err != nil {
266 return fmt.Errorf("failed to create a temp dir for zip packages: %v", err)
267 }
268 defer os.RemoveAll(tmpZipDir)
269 }
270 fileName = filepath.Join(tmpZipDir, info.Name()+".zip")
271 if err := packages.CreateZip(fileName, p); err != nil {
272 return err
273 }
274 }
275 objects[pkgName] = repository.BinaryServer(binaryInvoker(fileName))
276 envelope.Packages[info.Name()] = naming.Join(name, pkgName)
277 }
278
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800279 objects["application"] = repository.ApplicationServer(envelopeInvoker(envelope))
280 appName := naming.Join(name, "application")
Bogdan Caprita8964d3f2015-02-02 13:47:39 -0800281 appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride))
282 // Reset the value for any future invocations of "install" or
283 // "install-local" (we run more than one command per process in unit
284 // tests).
285 configOverride = configFlag{}
Bogdan Capritab646a1c2015-01-30 19:16:37 -0800286 if err != nil {
287 return fmt.Errorf("Install failed: %v", err)
288 }
289 fmt.Fprintf(cmd.Stdout(), "Successfully installed: %q\n", naming.Join(deviceName, appID))
290 return nil
291}