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