1package azblob
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"fmt"
8	"net/url"
9
10	"github.com/Azure/azure-pipeline-go/pipeline"
11)
12
13// A ContainerURL represents a URL to the Azure Storage container allowing you to manipulate its blobs.
14type ContainerURL struct {
15	client containerClient
16}
17
18// NewContainerURL creates a ContainerURL object using the specified URL and request policy pipeline.
19func NewContainerURL(url url.URL, p pipeline.Pipeline) ContainerURL {
20	client := newContainerClient(url, p)
21	return ContainerURL{client: client}
22}
23
24// URL returns the URL endpoint used by the ContainerURL object.
25func (c ContainerURL) URL() url.URL {
26	return c.client.URL()
27}
28
29// String returns the URL as a string.
30func (c ContainerURL) String() string {
31	u := c.URL()
32	return u.String()
33}
34
35// WithPipeline creates a new ContainerURL object identical to the source but with the specified request policy pipeline.
36func (c ContainerURL) WithPipeline(p pipeline.Pipeline) ContainerURL {
37	return NewContainerURL(c.URL(), p)
38}
39
40// NewBlobURL creates a new BlobURL object by concatenating blobName to the end of
41// ContainerURL's URL. The new BlobURL uses the same request policy pipeline as the ContainerURL.
42// To change the pipeline, create the BlobURL and then call its WithPipeline method passing in the
43// desired pipeline object. Or, call this package's NewBlobURL instead of calling this object's
44// NewBlobURL method.
45func (c ContainerURL) NewBlobURL(blobName string) BlobURL {
46	blobURL := appendToURLPath(c.URL(), blobName)
47	return NewBlobURL(blobURL, c.client.Pipeline())
48}
49
50// NewAppendBlobURL creates a new AppendBlobURL object by concatenating blobName to the end of
51// ContainerURL's URL. The new AppendBlobURL uses the same request policy pipeline as the ContainerURL.
52// To change the pipeline, create the AppendBlobURL and then call its WithPipeline method passing in the
53// desired pipeline object. Or, call this package's NewAppendBlobURL instead of calling this object's
54// NewAppendBlobURL method.
55func (c ContainerURL) NewAppendBlobURL(blobName string) AppendBlobURL {
56	blobURL := appendToURLPath(c.URL(), blobName)
57	return NewAppendBlobURL(blobURL, c.client.Pipeline())
58}
59
60// NewBlockBlobURL creates a new BlockBlobURL object by concatenating blobName to the end of
61// ContainerURL's URL. The new BlockBlobURL uses the same request policy pipeline as the ContainerURL.
62// To change the pipeline, create the BlockBlobURL and then call its WithPipeline method passing in the
63// desired pipeline object. Or, call this package's NewBlockBlobURL instead of calling this object's
64// NewBlockBlobURL method.
65func (c ContainerURL) NewBlockBlobURL(blobName string) BlockBlobURL {
66	blobURL := appendToURLPath(c.URL(), blobName)
67	return NewBlockBlobURL(blobURL, c.client.Pipeline())
68}
69
70// NewPageBlobURL creates a new PageBlobURL object by concatenating blobName to the end of
71// ContainerURL's URL. The new PageBlobURL uses the same request policy pipeline as the ContainerURL.
72// To change the pipeline, create the PageBlobURL and then call its WithPipeline method passing in the
73// desired pipeline object. Or, call this package's NewPageBlobURL instead of calling this object's
74// NewPageBlobURL method.
75func (c ContainerURL) NewPageBlobURL(blobName string) PageBlobURL {
76	blobURL := appendToURLPath(c.URL(), blobName)
77	return NewPageBlobURL(blobURL, c.client.Pipeline())
78}
79
80// Create creates a new container within a storage account. If a container with the same name already exists, the operation fails.
81// For more information, see https://docs.microsoft.com/rest/api/storageservices/create-container.
82func (c ContainerURL) Create(ctx context.Context, metadata Metadata, publicAccessType PublicAccessType) (*ContainerCreateResponse, error) {
83	return c.client.Create(ctx, nil, metadata, publicAccessType, nil)
84}
85
86// Delete marks the specified container for deletion. The container and any blobs contained within it are later deleted during garbage collection.
87// For more information, see https://docs.microsoft.com/rest/api/storageservices/delete-container.
88func (c ContainerURL) Delete(ctx context.Context, ac ContainerAccessConditions) (*ContainerDeleteResponse, error) {
89	if ac.IfMatch != ETagNone || ac.IfNoneMatch != ETagNone {
90		return nil, errors.New("the IfMatch and IfNoneMatch access conditions must have their default values because they are ignored by the service")
91	}
92
93	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.ModifiedAccessConditions.pointers()
94	return c.client.Delete(ctx, nil, ac.LeaseAccessConditions.pointers(),
95		ifModifiedSince, ifUnmodifiedSince, nil)
96}
97
98// GetProperties returns the container's properties.
99// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-container-metadata.
100func (c ContainerURL) GetProperties(ctx context.Context, ac LeaseAccessConditions) (*ContainerGetPropertiesResponse, error) {
101	// NOTE: GetMetadata actually calls GetProperties internally because GetProperties returns the metadata AND the properties.
102	// This allows us to not expose a GetProperties method at all simplifying the API.
103	return c.client.GetProperties(ctx, nil, ac.pointers(), nil)
104}
105
106// SetMetadata sets the container's metadata.
107// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-container-metadata.
108func (c ContainerURL) SetMetadata(ctx context.Context, metadata Metadata, ac ContainerAccessConditions) (*ContainerSetMetadataResponse, error) {
109	if !ac.IfUnmodifiedSince.IsZero() || ac.IfMatch != ETagNone || ac.IfNoneMatch != ETagNone {
110		return nil, errors.New("the IfUnmodifiedSince, IfMatch, and IfNoneMatch must have their default values because they are ignored by the blob service")
111	}
112	ifModifiedSince, _, _, _ := ac.ModifiedAccessConditions.pointers()
113	return c.client.SetMetadata(ctx, nil, ac.LeaseAccessConditions.pointers(), metadata, ifModifiedSince, nil)
114}
115
116// GetAccessPolicy returns the container's access policy. The access policy indicates whether container's blobs may be accessed publicly.
117// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-container-acl.
118func (c ContainerURL) GetAccessPolicy(ctx context.Context, ac LeaseAccessConditions) (*SignedIdentifiers, error) {
119	return c.client.GetAccessPolicy(ctx, nil, ac.pointers(), nil)
120}
121
122// The AccessPolicyPermission type simplifies creating the permissions string for a container's access policy.
123// Initialize an instance of this type and then call its String method to set AccessPolicy's Permission field.
124type AccessPolicyPermission struct {
125	Read, Add, Create, Write, Delete, List bool
126}
127
128// String produces the access policy permission string for an Azure Storage container.
129// Call this method to set AccessPolicy's Permission field.
130func (p AccessPolicyPermission) String() string {
131	var b bytes.Buffer
132	if p.Read {
133		b.WriteRune('r')
134	}
135	if p.Add {
136		b.WriteRune('a')
137	}
138	if p.Create {
139		b.WriteRune('c')
140	}
141	if p.Write {
142		b.WriteRune('w')
143	}
144	if p.Delete {
145		b.WriteRune('d')
146	}
147	if p.List {
148		b.WriteRune('l')
149	}
150	return b.String()
151}
152
153// Parse initializes the AccessPolicyPermission's fields from a string.
154func (p *AccessPolicyPermission) Parse(s string) error {
155	*p = AccessPolicyPermission{} // Clear the flags
156	for _, r := range s {
157		switch r {
158		case 'r':
159			p.Read = true
160		case 'a':
161			p.Add = true
162		case 'c':
163			p.Create = true
164		case 'w':
165			p.Write = true
166		case 'd':
167			p.Delete = true
168		case 'l':
169			p.List = true
170		default:
171			return fmt.Errorf("invalid permission: '%v'", r)
172		}
173	}
174	return nil
175}
176
177// SetAccessPolicy sets the container's permissions. The access policy indicates whether blobs in a container may be accessed publicly.
178// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-container-acl.
179func (c ContainerURL) SetAccessPolicy(ctx context.Context, accessType PublicAccessType, si []SignedIdentifier,
180	ac ContainerAccessConditions) (*ContainerSetAccessPolicyResponse, error) {
181	if ac.IfMatch != ETagNone || ac.IfNoneMatch != ETagNone {
182		return nil, errors.New("the IfMatch and IfNoneMatch access conditions must have their default values because they are ignored by the service")
183	}
184	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.ModifiedAccessConditions.pointers()
185	return c.client.SetAccessPolicy(ctx, si, nil, ac.LeaseAccessConditions.pointers(),
186		accessType, ifModifiedSince, ifUnmodifiedSince, nil)
187}
188
189// AcquireLease acquires a lease on the container for delete operations. The lease duration must be between 15 to 60 seconds, or infinite (-1).
190// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
191func (c ContainerURL) AcquireLease(ctx context.Context, proposedID string, duration int32, ac ModifiedAccessConditions) (*ContainerAcquireLeaseResponse, error) {
192	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
193	return c.client.AcquireLease(ctx, nil, &duration, &proposedID,
194		ifModifiedSince, ifUnmodifiedSince, nil)
195}
196
197// RenewLease renews the container's previously-acquired lease.
198// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
199func (c ContainerURL) RenewLease(ctx context.Context, leaseID string, ac ModifiedAccessConditions) (*ContainerRenewLeaseResponse, error) {
200	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
201	return c.client.RenewLease(ctx, leaseID, nil, ifModifiedSince, ifUnmodifiedSince, nil)
202}
203
204// ReleaseLease releases the container's previously-acquired lease.
205// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
206func (c ContainerURL) ReleaseLease(ctx context.Context, leaseID string, ac ModifiedAccessConditions) (*ContainerReleaseLeaseResponse, error) {
207	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
208	return c.client.ReleaseLease(ctx, leaseID, nil, ifModifiedSince, ifUnmodifiedSince, nil)
209}
210
211// BreakLease breaks the container's previously-acquired lease (if it exists).
212// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
213func (c ContainerURL) BreakLease(ctx context.Context, period int32, ac ModifiedAccessConditions) (*ContainerBreakLeaseResponse, error) {
214	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
215	return c.client.BreakLease(ctx, nil, leasePeriodPointer(period), ifModifiedSince, ifUnmodifiedSince, nil)
216}
217
218// ChangeLease changes the container's lease ID.
219// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
220func (c ContainerURL) ChangeLease(ctx context.Context, leaseID string, proposedID string, ac ModifiedAccessConditions) (*ContainerChangeLeaseResponse, error) {
221	ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
222	return c.client.ChangeLease(ctx, leaseID, proposedID, nil, ifModifiedSince, ifUnmodifiedSince, nil)
223}
224
225// ListBlobsFlatSegment returns a single segment of blobs starting from the specified Marker. Use an empty
226// Marker to start enumeration from the beginning. Blob names are returned in lexicographic order.
227// After getting a segment, process it, and then call ListBlobsFlatSegment again (passing the the
228// previously-returned Marker) to get the next segment.
229// For more information, see https://docs.microsoft.com/rest/api/storageservices/list-blobs.
230func (c ContainerURL) ListBlobsFlatSegment(ctx context.Context, marker Marker, o ListBlobsSegmentOptions) (*ListBlobsFlatSegmentResponse, error) {
231	prefix, include, maxResults := o.pointers()
232	return c.client.ListBlobFlatSegment(ctx, prefix, marker.Val, maxResults, include, nil, nil)
233}
234
235// ListBlobsHierarchySegment returns a single segment of blobs starting from the specified Marker. Use an empty
236// Marker to start enumeration from the beginning. Blob names are returned in lexicographic order.
237// After getting a segment, process it, and then call ListBlobsHierarchicalSegment again (passing the the
238// previously-returned Marker) to get the next segment.
239// For more information, see https://docs.microsoft.com/rest/api/storageservices/list-blobs.
240func (c ContainerURL) ListBlobsHierarchySegment(ctx context.Context, marker Marker, delimiter string, o ListBlobsSegmentOptions) (*ListBlobsHierarchySegmentResponse, error) {
241	if o.Details.Snapshots {
242		return nil, errors.New("snapshots are not supported in this listing operation")
243	}
244	prefix, include, maxResults := o.pointers()
245	return c.client.ListBlobHierarchySegment(ctx, delimiter, prefix, marker.Val, maxResults, include, nil, nil)
246}
247
248// ListBlobsSegmentOptions defines options available when calling ListBlobs.
249type ListBlobsSegmentOptions struct {
250	Details BlobListingDetails // No IncludeType header is produced if ""
251	Prefix  string             // No Prefix header is produced if ""
252
253	// SetMaxResults sets the maximum desired results you want the service to return. Note, the
254	// service may return fewer results than requested.
255	// MaxResults=0 means no 'MaxResults' header specified.
256	MaxResults int32
257}
258
259func (o *ListBlobsSegmentOptions) pointers() (prefix *string, include []ListBlobsIncludeItemType, maxResults *int32) {
260	if o.Prefix != "" {
261		prefix = &o.Prefix
262	}
263	include = o.Details.slice()
264	if o.MaxResults != 0 {
265		maxResults = &o.MaxResults
266	}
267	return
268}
269
270// BlobListingDetails indicates what additional information the service should return with each blob.
271type BlobListingDetails struct {
272	Copy, Metadata, Snapshots, UncommittedBlobs, Deleted bool
273}
274
275// string produces the Include query parameter's value.
276func (d *BlobListingDetails) slice() []ListBlobsIncludeItemType {
277	items := []ListBlobsIncludeItemType{}
278	// NOTE: Multiple strings MUST be appended in alphabetic order or signing the string for authentication fails!
279	if d.Copy {
280		items = append(items, ListBlobsIncludeItemCopy)
281	}
282	if d.Deleted {
283		items = append(items, ListBlobsIncludeItemDeleted)
284	}
285	if d.Metadata {
286		items = append(items, ListBlobsIncludeItemMetadata)
287	}
288	if d.Snapshots {
289		items = append(items, ListBlobsIncludeItemSnapshots)
290	}
291	if d.UncommittedBlobs {
292		items = append(items, ListBlobsIncludeItemUncommittedblobs)
293	}
294	return items
295}
296