github.com/leeclow-ops/gophercloud@v1.2.1/openstack/objectstorage/v1/objects/requests.go (about) 1 package objects 2 3 import ( 4 "bytes" 5 "crypto/hmac" 6 "crypto/md5" 7 "crypto/sha1" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "strings" 12 "time" 13 14 "github.com/leeclow-ops/gophercloud" 15 "github.com/leeclow-ops/gophercloud/openstack/objectstorage/v1/accounts" 16 "github.com/leeclow-ops/gophercloud/openstack/objectstorage/v1/containers" 17 "github.com/leeclow-ops/gophercloud/pagination" 18 ) 19 20 // ListOptsBuilder allows extensions to add additional parameters to the List 21 // request. 22 type ListOptsBuilder interface { 23 ToObjectListParams() (bool, string, error) 24 } 25 26 // ListOpts is a structure that holds parameters for listing objects. 27 type ListOpts struct { 28 // Full is a true/false value that represents the amount of object information 29 // returned. If Full is set to true, then the content-type, number of bytes, 30 // hash date last modified, and name are returned. If set to false or not set, 31 // then only the object names are returned. 32 Full bool 33 Limit int `q:"limit"` 34 Marker string `q:"marker"` 35 EndMarker string `q:"end_marker"` 36 Format string `q:"format"` 37 Prefix string `q:"prefix"` 38 Delimiter string `q:"delimiter"` 39 Path string `q:"path"` 40 Versions bool `q:"versions"` 41 } 42 43 // ToObjectListParams formats a ListOpts into a query string and boolean 44 // representing whether to list complete information for each object. 45 func (opts ListOpts) ToObjectListParams() (bool, string, error) { 46 q, err := gophercloud.BuildQueryString(opts) 47 return opts.Full, q.String(), err 48 } 49 50 // List is a function that retrieves all objects in a container. It also returns 51 // the details for the container. To extract only the object information or names, 52 // pass the ListResult response to the ExtractInfo or ExtractNames function, 53 // respectively. 54 func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager { 55 url, err := listURL(c, containerName) 56 if err != nil { 57 return pagination.Pager{Err: err} 58 } 59 60 headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"} 61 if opts != nil { 62 full, query, err := opts.ToObjectListParams() 63 if err != nil { 64 return pagination.Pager{Err: err} 65 } 66 url += query 67 68 if full { 69 headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"} 70 } 71 } 72 73 pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { 74 p := ObjectPage{pagination.MarkerPageBase{PageResult: r}} 75 p.MarkerPageBase.Owner = p 76 return p 77 }) 78 pager.Headers = headers 79 return pager 80 } 81 82 // DownloadOptsBuilder allows extensions to add additional parameters to the 83 // Download request. 84 type DownloadOptsBuilder interface { 85 ToObjectDownloadParams() (map[string]string, string, error) 86 } 87 88 // DownloadOpts is a structure that holds parameters for downloading an object. 89 type DownloadOpts struct { 90 IfMatch string `h:"If-Match"` 91 IfModifiedSince time.Time `h:"If-Modified-Since"` 92 IfNoneMatch string `h:"If-None-Match"` 93 IfUnmodifiedSince time.Time `h:"If-Unmodified-Since"` 94 Newest bool `h:"X-Newest"` 95 Range string `h:"Range"` 96 Expires string `q:"expires"` 97 MultipartManifest string `q:"multipart-manifest"` 98 Signature string `q:"signature"` 99 ObjectVersionID string `q:"version-id"` 100 } 101 102 // ToObjectDownloadParams formats a DownloadOpts into a query string and map of 103 // headers. 104 func (opts DownloadOpts) ToObjectDownloadParams() (map[string]string, string, error) { 105 q, err := gophercloud.BuildQueryString(opts) 106 if err != nil { 107 return nil, "", err 108 } 109 h, err := gophercloud.BuildHeaders(opts) 110 if err != nil { 111 return nil, q.String(), err 112 } 113 if !opts.IfModifiedSince.IsZero() { 114 h["If-Modified-Since"] = opts.IfModifiedSince.Format(time.RFC1123) 115 } 116 if !opts.IfUnmodifiedSince.IsZero() { 117 h["If-Unmodified-Since"] = opts.IfUnmodifiedSince.Format(time.RFC1123) 118 } 119 return h, q.String(), nil 120 } 121 122 // Download is a function that retrieves the content and metadata for an object. 123 // To extract just the content, call the DownloadResult method ExtractContent, 124 // after checking DownloadResult's Err field. 125 func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) (r DownloadResult) { 126 url, err := downloadURL(c, containerName, objectName) 127 if err != nil { 128 r.Err = err 129 return 130 } 131 132 h := make(map[string]string) 133 if opts != nil { 134 headers, query, err := opts.ToObjectDownloadParams() 135 if err != nil { 136 r.Err = err 137 return 138 } 139 for k, v := range headers { 140 h[k] = v 141 } 142 url += query 143 } 144 145 resp, err := c.Get(url, nil, &gophercloud.RequestOpts{ 146 MoreHeaders: h, 147 OkCodes: []int{200, 206, 304}, 148 KeepResponseBody: true, 149 }) 150 r.Body, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 151 return 152 } 153 154 // CreateOptsBuilder allows extensions to add additional parameters to the 155 // Create request. 156 type CreateOptsBuilder interface { 157 ToObjectCreateParams() (io.Reader, map[string]string, string, error) 158 } 159 160 // CreateOpts is a structure that holds parameters for creating an object. 161 type CreateOpts struct { 162 Content io.Reader 163 Metadata map[string]string 164 NoETag bool 165 CacheControl string `h:"Cache-Control"` 166 ContentDisposition string `h:"Content-Disposition"` 167 ContentEncoding string `h:"Content-Encoding"` 168 ContentLength int64 `h:"Content-Length"` 169 ContentType string `h:"Content-Type"` 170 CopyFrom string `h:"X-Copy-From"` 171 DeleteAfter int64 `h:"X-Delete-After"` 172 DeleteAt int64 `h:"X-Delete-At"` 173 DetectContentType string `h:"X-Detect-Content-Type"` 174 ETag string `h:"ETag"` 175 IfNoneMatch string `h:"If-None-Match"` 176 ObjectManifest string `h:"X-Object-Manifest"` 177 TransferEncoding string `h:"Transfer-Encoding"` 178 Expires string `q:"expires"` 179 MultipartManifest string `q:"multipart-manifest"` 180 Signature string `q:"signature"` 181 } 182 183 // ToObjectCreateParams formats a CreateOpts into a query string and map of 184 // headers. 185 func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, string, error) { 186 q, err := gophercloud.BuildQueryString(opts) 187 if err != nil { 188 return nil, nil, "", err 189 } 190 h, err := gophercloud.BuildHeaders(opts) 191 if err != nil { 192 return nil, nil, "", err 193 } 194 195 for k, v := range opts.Metadata { 196 h["X-Object-Meta-"+k] = v 197 } 198 199 if opts.NoETag { 200 delete(h, "etag") 201 return opts.Content, h, q.String(), nil 202 } 203 204 if h["ETag"] != "" { 205 return opts.Content, h, q.String(), nil 206 } 207 208 // When we're dealing with big files an io.ReadSeeker allows us to efficiently calculate 209 // the md5 sum. An io.Reader is only readable once which means we have to copy the entire 210 // file content into memory first. 211 readSeeker, isReadSeeker := opts.Content.(io.ReadSeeker) 212 if !isReadSeeker { 213 data, err := ioutil.ReadAll(opts.Content) 214 if err != nil { 215 return nil, nil, "", err 216 } 217 readSeeker = bytes.NewReader(data) 218 } 219 220 hash := md5.New() 221 // io.Copy into md5 is very efficient as it's done in small chunks. 222 if _, err := io.Copy(hash, readSeeker); err != nil { 223 return nil, nil, "", err 224 } 225 readSeeker.Seek(0, io.SeekStart) 226 227 h["ETag"] = fmt.Sprintf("%x", hash.Sum(nil)) 228 229 return readSeeker, h, q.String(), nil 230 } 231 232 // Create is a function that creates a new object or replaces an existing 233 // object. If the returned response's ETag header fails to match the local 234 // checksum, the failed request will automatically be retried up to a maximum 235 // of 3 times. 236 func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) { 237 url, err := createURL(c, containerName, objectName) 238 if err != nil { 239 r.Err = err 240 return 241 } 242 h := make(map[string]string) 243 var b io.Reader 244 if opts != nil { 245 tmpB, headers, query, err := opts.ToObjectCreateParams() 246 if err != nil { 247 r.Err = err 248 return 249 } 250 for k, v := range headers { 251 h[k] = v 252 } 253 url += query 254 b = tmpB 255 } 256 257 resp, err := c.Put(url, b, nil, &gophercloud.RequestOpts{ 258 MoreHeaders: h, 259 }) 260 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 261 return 262 } 263 264 // CopyOptsBuilder allows extensions to add additional parameters to the 265 // Copy request. 266 type CopyOptsBuilder interface { 267 ToObjectCopyMap() (map[string]string, error) 268 } 269 270 // CopyOptsQueryBuilder allows extensions to add additional query parameters to 271 // the Copy request. 272 type CopyOptsQueryBuilder interface { 273 ToObjectCopyQuery() (string, error) 274 } 275 276 // CopyOpts is a structure that holds parameters for copying one object to 277 // another. 278 type CopyOpts struct { 279 Metadata map[string]string 280 ContentDisposition string `h:"Content-Disposition"` 281 ContentEncoding string `h:"Content-Encoding"` 282 ContentType string `h:"Content-Type"` 283 Destination string `h:"Destination" required:"true"` 284 ObjectVersionID string `q:"version-id"` 285 } 286 287 // ToObjectCopyMap formats a CopyOpts into a map of headers. 288 func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) { 289 h, err := gophercloud.BuildHeaders(opts) 290 if err != nil { 291 return nil, err 292 } 293 for k, v := range opts.Metadata { 294 h["X-Object-Meta-"+k] = v 295 } 296 return h, nil 297 } 298 299 // ToObjectCopyQuery formats a CopyOpts into a query. 300 func (opts CopyOpts) ToObjectCopyQuery() (string, error) { 301 q, err := gophercloud.BuildQueryString(opts) 302 if err != nil { 303 return "", err 304 } 305 return q.String(), nil 306 } 307 308 // Copy is a function that copies one object to another. 309 func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) (r CopyResult) { 310 url, err := copyURL(c, containerName, objectName) 311 if err != nil { 312 r.Err = err 313 return 314 } 315 316 h := make(map[string]string) 317 headers, err := opts.ToObjectCopyMap() 318 if err != nil { 319 r.Err = err 320 return 321 } 322 for k, v := range headers { 323 h[k] = v 324 } 325 326 if opts, ok := opts.(CopyOptsQueryBuilder); ok { 327 query, err := opts.ToObjectCopyQuery() 328 if err != nil { 329 r.Err = err 330 return 331 } 332 url += query 333 } 334 335 resp, err := c.Request("COPY", url, &gophercloud.RequestOpts{ 336 MoreHeaders: h, 337 OkCodes: []int{201}, 338 }) 339 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 340 return 341 } 342 343 // DeleteOptsBuilder allows extensions to add additional parameters to the 344 // Delete request. 345 type DeleteOptsBuilder interface { 346 ToObjectDeleteQuery() (string, error) 347 } 348 349 // DeleteOpts is a structure that holds parameters for deleting an object. 350 type DeleteOpts struct { 351 MultipartManifest string `q:"multipart-manifest"` 352 ObjectVersionID string `q:"version-id"` 353 } 354 355 // ToObjectDeleteQuery formats a DeleteOpts into a query string. 356 func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) { 357 q, err := gophercloud.BuildQueryString(opts) 358 return q.String(), err 359 } 360 361 // Delete is a function that deletes an object. 362 func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) { 363 url, err := deleteURL(c, containerName, objectName) 364 if err != nil { 365 r.Err = err 366 return 367 } 368 if opts != nil { 369 query, err := opts.ToObjectDeleteQuery() 370 if err != nil { 371 r.Err = err 372 return 373 } 374 url += query 375 } 376 resp, err := c.Delete(url, nil) 377 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 378 return 379 } 380 381 // GetOptsBuilder allows extensions to add additional parameters to the 382 // Get request. 383 type GetOptsBuilder interface { 384 ToObjectGetParams() (map[string]string, string, error) 385 } 386 387 // GetOpts is a structure that holds parameters for getting an object's 388 // metadata. 389 type GetOpts struct { 390 Newest bool `h:"X-Newest"` 391 Expires string `q:"expires"` 392 Signature string `q:"signature"` 393 ObjectVersionID string `q:"version-id"` 394 } 395 396 // ToObjectGetParams formats a GetOpts into a query string and a map of headers. 397 func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) { 398 q, err := gophercloud.BuildQueryString(opts) 399 if err != nil { 400 return nil, "", err 401 } 402 h, err := gophercloud.BuildHeaders(opts) 403 if err != nil { 404 return nil, q.String(), err 405 } 406 return h, q.String(), nil 407 } 408 409 // Get is a function that retrieves the metadata of an object. To extract just 410 // the custom metadata, pass the GetResult response to the ExtractMetadata 411 // function. 412 func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { 413 url, err := getURL(c, containerName, objectName) 414 if err != nil { 415 r.Err = err 416 return 417 } 418 h := make(map[string]string) 419 if opts != nil { 420 headers, query, err := opts.ToObjectGetParams() 421 if err != nil { 422 r.Err = err 423 return 424 } 425 for k, v := range headers { 426 h[k] = v 427 } 428 url += query 429 } 430 431 resp, err := c.Head(url, &gophercloud.RequestOpts{ 432 MoreHeaders: h, 433 OkCodes: []int{200, 204}, 434 }) 435 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 436 return 437 } 438 439 // UpdateOptsBuilder allows extensions to add additional parameters to the 440 // Update request. 441 type UpdateOptsBuilder interface { 442 ToObjectUpdateMap() (map[string]string, error) 443 } 444 445 // UpdateOpts is a structure that holds parameters for updating, creating, or 446 // deleting an object's metadata. 447 type UpdateOpts struct { 448 Metadata map[string]string 449 RemoveMetadata []string 450 ContentDisposition *string `h:"Content-Disposition"` 451 ContentEncoding *string `h:"Content-Encoding"` 452 ContentType *string `h:"Content-Type"` 453 DeleteAfter *int64 `h:"X-Delete-After"` 454 DeleteAt *int64 `h:"X-Delete-At"` 455 DetectContentType *bool `h:"X-Detect-Content-Type"` 456 } 457 458 // ToObjectUpdateMap formats a UpdateOpts into a map of headers. 459 func (opts UpdateOpts) ToObjectUpdateMap() (map[string]string, error) { 460 h, err := gophercloud.BuildHeaders(opts) 461 if err != nil { 462 return nil, err 463 } 464 465 for k, v := range opts.Metadata { 466 h["X-Object-Meta-"+k] = v 467 } 468 469 for _, k := range opts.RemoveMetadata { 470 h["X-Remove-Object-Meta-"+k] = "remove" 471 } 472 return h, nil 473 } 474 475 // Update is a function that creates, updates, or deletes an object's metadata. 476 func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) (r UpdateResult) { 477 url, err := updateURL(c, containerName, objectName) 478 if err != nil { 479 r.Err = err 480 return 481 } 482 h := make(map[string]string) 483 if opts != nil { 484 headers, err := opts.ToObjectUpdateMap() 485 if err != nil { 486 r.Err = err 487 return 488 } 489 490 for k, v := range headers { 491 h[k] = v 492 } 493 } 494 resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{ 495 MoreHeaders: h, 496 }) 497 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 498 return 499 } 500 501 // HTTPMethod represents an HTTP method string (e.g. "GET"). 502 type HTTPMethod string 503 504 var ( 505 // GET represents an HTTP "GET" method. 506 GET HTTPMethod = "GET" 507 508 // POST represents an HTTP "POST" method. 509 POST HTTPMethod = "POST" 510 ) 511 512 // CreateTempURLOpts are options for creating a temporary URL for an object. 513 type CreateTempURLOpts struct { 514 // (REQUIRED) Method is the HTTP method to allow for users of the temp URL. 515 // Valid values are "GET" and "POST". 516 Method HTTPMethod 517 518 // (REQUIRED) TTL is the number of seconds the temp URL should be active. 519 TTL int 520 521 // (Optional) Split is the string on which to split the object URL. Since only 522 // the object path is used in the hash, the object URL needs to be parsed. If 523 // empty, the default OpenStack URL split point will be used ("/v1/"). 524 Split string 525 526 // Timestamp is a timestamp to calculate Temp URL signature. Optional. 527 Timestamp time.Time 528 } 529 530 // CreateTempURL is a function for creating a temporary URL for an object. It 531 // allows users to have "GET" or "POST" access to a particular tenant's object 532 // for a limited amount of time. 533 func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateTempURLOpts) (string, error) { 534 url, err := getURL(c, containerName, objectName) 535 if err != nil { 536 return "", err 537 } 538 539 if opts.Split == "" { 540 opts.Split = "/v1/" 541 } 542 543 // Initialize time if it was not passed as opts 544 var date time.Time 545 if opts.Timestamp.IsZero() { 546 date = time.Now().UTC() 547 } else { 548 date = opts.Timestamp 549 } 550 551 duration := time.Duration(opts.TTL) * time.Second 552 expiry := date.Add(duration).Unix() 553 getHeader, err := containers.Get(c, containerName, nil).Extract() 554 if err != nil { 555 return "", err 556 } 557 tempURLKey := getHeader.TempURLKey 558 if tempURLKey == "" { 559 // fallback to an account TempURL key 560 getHeader, err := accounts.Get(c, nil).Extract() 561 if err != nil { 562 return "", err 563 } 564 tempURLKey = getHeader.TempURLKey 565 } 566 secretKey := []byte(tempURLKey) 567 splitPath := strings.Split(url, opts.Split) 568 baseURL, objectPath := splitPath[0], splitPath[1] 569 objectPath = opts.Split + objectPath 570 body := fmt.Sprintf("%s\n%d\n%s", opts.Method, expiry, objectPath) 571 hash := hmac.New(sha1.New, secretKey) 572 hash.Write([]byte(body)) 573 hexsum := fmt.Sprintf("%x", hash.Sum(nil)) 574 return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil 575 } 576 577 // BulkDelete is a function that bulk deletes objects. 578 func BulkDelete(c *gophercloud.ServiceClient, container string, objects []string) (r BulkDeleteResult) { 579 // urlencode object names to be on the safe side 580 // https://github.com/openstack/swift/blob/stable/train/swift/common/middleware/bulk.py#L160 581 // https://github.com/openstack/swift/blob/stable/train/swift/common/swob.py#L302 582 encodedObjects := make([]string, len(objects)) 583 for i, v := range objects { 584 encodedObjects[i] = strings.Join([]string{container, v}, "/") 585 } 586 b := strings.NewReader(strings.Join(encodedObjects, "\n") + "\n") 587 resp, err := c.Post(bulkDeleteURL(c), b, &r.Body, &gophercloud.RequestOpts{ 588 MoreHeaders: map[string]string{ 589 "Accept": "application/json", 590 "Content-Type": "text/plain", 591 }, 592 OkCodes: []int{200}, 593 }) 594 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 595 return 596 }