github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/resource/cmd/upload.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cmd 5 6 import ( 7 "io" 8 9 "github.com/juju/cmd" 10 "github.com/juju/errors" 11 12 "github.com/juju/juju/cmd/modelcmd" 13 ) 14 15 // UploadClient has the API client methods needed by UploadCommand. 16 type UploadClient interface { 17 // Upload sends the resource to Juju. 18 Upload(service, name, filename string, resource io.ReadSeeker) error 19 20 // Close closes the client. 21 Close() error 22 } 23 24 // ReadSeekCloser combines 2 interfaces. 25 type ReadSeekCloser interface { 26 io.ReadCloser 27 io.Seeker 28 } 29 30 // UploadDeps is a type that contains external functions that Upload depends on 31 // to function. 32 type UploadDeps struct { 33 // NewClient returns the value that wraps the API for uploading to the server. 34 NewClient func(*UploadCommand) (UploadClient, error) 35 36 // OpenResource handles creating a reader from the resource path. 37 OpenResource func(path string) (ReadSeekCloser, error) 38 } 39 40 // UploadCommand implements the upload command. 41 type UploadCommand struct { 42 deps UploadDeps 43 modelcmd.ModelCommandBase 44 service string 45 resourceFile resourceFile 46 } 47 48 // NewUploadCommand returns a new command that lists resources defined 49 // by a charm. 50 func NewUploadCommand(deps UploadDeps) *UploadCommand { 51 return &UploadCommand{deps: deps} 52 } 53 54 // Info implements cmd.Command.Info 55 func (c *UploadCommand) Info() *cmd.Info { 56 return &cmd.Info{ 57 Name: "attach", 58 Args: "application name=file", 59 Purpose: "upload a file as a resource for an application", 60 Doc: ` 61 This command uploads a file from your local disk to the juju controller to be 62 used as a resource for an application. 63 `, 64 } 65 } 66 67 // Init implements cmd.Command.Init. It will return an error satisfying 68 // errors.BadRequest if you give it an incorrect number of arguments. 69 func (c *UploadCommand) Init(args []string) error { 70 switch len(args) { 71 case 0: 72 return errors.BadRequestf("missing application name") 73 case 1: 74 return errors.BadRequestf("no resource specified") 75 } 76 77 service := args[0] 78 if service == "" { // TODO(ericsnow) names.IsValidApplication 79 return errors.NewNotValid(nil, "missing application name") 80 } 81 c.service = service 82 83 if err := c.addResourceFile(args[1]); err != nil { 84 return errors.Trace(err) 85 } 86 if err := cmd.CheckEmpty(args[2:]); err != nil { 87 return errors.NewBadRequest(err, "") 88 } 89 90 return nil 91 } 92 93 // addResourceFile parses the given arg into a name and a resource file, 94 // and saves it in c.resourceFiles. 95 func (c *UploadCommand) addResourceFile(arg string) error { 96 name, filename, err := parseResourceFileArg(arg) 97 if err != nil { 98 return errors.Annotatef(err, "bad resource arg %q", arg) 99 } 100 c.resourceFile = resourceFile{ 101 service: c.service, 102 name: name, 103 filename: filename, 104 } 105 106 return nil 107 } 108 109 // Run implements cmd.Command.Run. 110 func (c *UploadCommand) Run(*cmd.Context) error { 111 apiclient, err := c.deps.NewClient(c) 112 if err != nil { 113 return errors.Annotatef(err, "can't connect to %s", c.ConnectionName()) 114 } 115 defer apiclient.Close() 116 117 if err := c.upload(c.resourceFile, apiclient); err != nil { 118 return errors.Annotatef(err, "failed to upload resource %q", c.resourceFile.name) 119 } 120 return nil 121 } 122 123 // upload opens the given file and calls the apiclient to upload it to the given 124 // application with the given name. 125 func (c *UploadCommand) upload(rf resourceFile, client UploadClient) error { 126 f, err := c.deps.OpenResource(rf.filename) 127 if err != nil { 128 return errors.Trace(err) 129 } 130 defer f.Close() 131 err = client.Upload(rf.service, rf.name, rf.filename, f) 132 return errors.Trace(err) 133 }