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