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