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  }