Skip to content

Commit 49bc572

Browse files
committed
support io.Reader with storage import
* request: SourceLocation is now untyped to allow passing of io.Reader instead of just string. This gives caller more flexibility while still providing ease of use. A new ContentType field allows the caller to specify if the locally available image is compressed or not. * service: support io.Reader by type asserting the SourceLocation. If it's string then it's used as a file name. Otherwise the io.Reader is passed as-is. Content type is set according to the request. * client: support setting and getting of content type. Use the Set method instead of Add to reset the specified headers.
1 parent 02a65fe commit 49bc572

File tree

3 files changed

+60
-18
lines changed

3 files changed

+60
-18
lines changed

upcloud/client/client.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ const (
2323

2424
// Client represents an API client
2525
type Client struct {
26-
userName string
27-
password string
28-
httpClient *http.Client
26+
userName string
27+
password string
28+
httpClient *http.Client
29+
contentType string
2930
}
3031

3132
// New creates ands returns a new client configured with the specified user and password
@@ -150,11 +151,22 @@ func (c *Client) PerformJSONPutUploadRequest(url string, requestBody io.Reader)
150151
return c.performJSONRequest(request)
151152
}
152153

154+
func (c *Client) SetContentType(ct string) {
155+
c.contentType = ct
156+
}
157+
158+
func (c *Client) GetContentType() string {
159+
if c.contentType == "" {
160+
return "application/json"
161+
}
162+
return c.contentType
163+
}
164+
153165
// Adds common headers to the specified request
154166
func (c *Client) addJSONRequestHeaders(request *http.Request) *http.Request {
155167
request.SetBasicAuth(c.userName, c.password)
156-
request.Header.Add("Accept", "application/json")
157-
request.Header.Add("Content-Type", "application/json")
168+
request.Header.Set("Accept", "application/json")
169+
request.Header.Set("Content-Type", c.GetContentType())
158170

159171
return request
160172
}

upcloud/request/storage.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func (r DetachStorageRequest) MarshalJSON() ([]byte, error) {
154154
return json.Marshal(&v)
155155
}
156156

157-
//DeleteStorageRequest represents a request to delete a storage device
157+
// DeleteStorageRequest represents a request to delete a storage device
158158
type DeleteStorageRequest struct {
159159
UUID string
160160
}
@@ -289,12 +289,18 @@ func (r *RestoreBackupRequest) RequestURL() string {
289289
return fmt.Sprintf("/storage/%s/restore", r.UUID)
290290
}
291291

292+
// ImportSourceLocation can be a string to a file or io.Reader in StorageImportSourceDirectUpload mode or a URL
293+
// in StorageImportSourceHTTPImport mode
294+
type ImportSourceLocation interface{}
295+
292296
// CreateStorageImportRequest represent a request to import storage.
293297
type CreateStorageImportRequest struct {
294298
StorageUUID string `json:"-"`
299+
// ContentType can be given when using the StorageImportSourceDirectUpload mode
300+
ContentType string `json:"-"`
295301

296-
Source string `json:"source"`
297-
SourceLocation string `json:"source_location,omitempty"`
302+
Source string `json:"source"`
303+
SourceLocation ImportSourceLocation `json:"source_location,omitempty"`
298304
}
299305

300306
// MarshalJSON is a custom marshaller that deals with

upcloud/service/storage.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7+
"io"
78
"os"
89
"time"
910

@@ -227,11 +228,20 @@ func (s *Service) RestoreBackup(r *request.RestoreBackupRequest) error {
227228
// CreateStorageImport begins the process of importing an image onto a storage device. A `upcloud.StorageImportSourceHTTPImport` source
228229
// will import from an HTTP source. `upcloud.StorageImportSourceDirectUpload` will directly upload the file specified in `SourceLocation`.
229230
func (s *Service) CreateStorageImport(r *request.CreateStorageImportRequest) (*upcloud.StorageImportDetails, error) {
230-
231231
if r.Source == request.StorageImportSourceDirectUpload {
232-
return s.directStorageImport(r)
232+
switch r.SourceLocation.(type) {
233+
case string, io.Reader:
234+
return s.directStorageImport(r)
235+
case nil:
236+
return nil, errors.New("SourceLocation must be specified")
237+
default:
238+
return nil, fmt.Errorf("unsupported storage source location type %T", r.SourceLocation)
239+
}
233240
}
234241

242+
if _, isString := r.SourceLocation.(string); !isString {
243+
return nil, fmt.Errorf("unsupported storage source location type %T", r.Source)
244+
}
235245
return s.doCreateStorageImport(r)
236246
}
237247

@@ -253,15 +263,24 @@ func (s *Service) doCreateStorageImport(r *request.CreateStorageImportRequest) (
253263
// directStorageImport handles the direct upload logic including getting the upload URL and PUT the file data
254264
// to that endpoint.
255265
func (s *Service) directStorageImport(r *request.CreateStorageImportRequest) (*upcloud.StorageImportDetails, error) {
256-
if r.SourceLocation == "" {
257-
return nil, errors.New("SourceLocation must be specified")
258-
}
266+
var bodyReader io.Reader
259267

260-
f, err := os.Open(r.SourceLocation)
261-
if err != nil {
262-
return nil, fmt.Errorf("unable to open SourceLocation: %w", err)
268+
switch v := r.SourceLocation.(type) {
269+
case string:
270+
if v == "" {
271+
return nil, errors.New("SourceLocation must be specified")
272+
}
273+
f, err := os.Open(v)
274+
if err != nil {
275+
return nil, fmt.Errorf("unable to open SourceLocation: %w", err)
276+
}
277+
bodyReader = f
278+
defer f.Close()
279+
case io.Reader:
280+
bodyReader = v
281+
default:
282+
return nil, fmt.Errorf("unsupported source location type %T", r.SourceLocation)
263283
}
264-
defer f.Close()
265284

266285
r.SourceLocation = ""
267286
storageImport, err := s.doCreateStorageImport(r)
@@ -273,10 +292,15 @@ func (s *Service) directStorageImport(r *request.CreateStorageImportRequest) (*u
273292
return nil, errors.New("no DirectUploadURL found in response")
274293
}
275294

276-
_, err = s.client.PerformJSONPutUploadRequest(storageImport.DirectUploadURL, f)
295+
curContentType := s.client.GetContentType()
296+
if r.ContentType != "" {
297+
s.client.SetContentType(r.ContentType)
298+
}
299+
_, err = s.client.PerformJSONPutUploadRequest(storageImport.DirectUploadURL, bodyReader)
277300
if err != nil {
278301
return nil, err
279302
}
303+
s.client.SetContentType(curContentType)
280304

281305
storageImport, err = s.GetStorageImportDetails(&request.GetStorageImportDetailsRequest{
282306
UUID: r.StorageUUID,

0 commit comments

Comments
 (0)