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  }