-
Notifications
You must be signed in to change notification settings - Fork 797
/
zc_service_client.go
281 lines (243 loc) · 12.4 KB
/
zc_service_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//go:build go1.18
// +build go1.18
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azblob
import (
"context"
"errors"
"net/url"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
)
//nolint
const (
// ContainerNameRoot is the special Azure Storage name used to identify a storage account's root container.
ContainerNameRoot = "$root"
// ContainerNameLogs is the special Azure Storage name used to identify a storage account's logs container.
ContainerNameLogs = "$logs"
)
// ServiceClient represents a URL to the Azure Blob Storage service allowing you to manipulate blob containers.
type ServiceClient struct {
client *serviceClient
sharedKey *SharedKeyCredential
}
// URL returns the URL endpoint used by the ServiceClient object.
func (s ServiceClient) URL() string {
return s.client.endpoint
}
// NewServiceClient creates a ServiceClient object using the specified URL, Azure AD credential, and options.
// Example of serviceURL: https://<your_storage_account>.blob.core.windows.net
func NewServiceClient(serviceURL string, cred azcore.TokenCredential, options *ClientOptions) (*ServiceClient, error) {
authPolicy := runtime.NewBearerTokenPolicy(cred, []string{tokenScope}, nil)
conOptions := getConnectionOptions(options)
conOptions.PerRetryPolicies = append(conOptions.PerRetryPolicies, authPolicy)
conn := newConnection(serviceURL, conOptions)
return &ServiceClient{
client: newServiceClient(conn.Endpoint(), conn.Pipeline()),
}, nil
}
// NewServiceClientWithNoCredential creates a ServiceClient object using the specified URL and options.
// Example of serviceURL: https://<your_storage_account>.blob.core.windows.net?<SAS token>
func NewServiceClientWithNoCredential(serviceURL string, options *ClientOptions) (*ServiceClient, error) {
conOptions := getConnectionOptions(options)
conn := newConnection(serviceURL, conOptions)
return &ServiceClient{
client: newServiceClient(conn.Endpoint(), conn.Pipeline()),
}, nil
}
// NewServiceClientWithSharedKey creates a ServiceClient object using the specified URL, shared key, and options.
// Example of serviceURL: https://<your_storage_account>.blob.core.windows.net
func NewServiceClientWithSharedKey(serviceURL string, cred *SharedKeyCredential, options *ClientOptions) (*ServiceClient, error) {
authPolicy := newSharedKeyCredPolicy(cred)
conOptions := getConnectionOptions(options)
conOptions.PerRetryPolicies = append(conOptions.PerRetryPolicies, authPolicy)
conn := newConnection(serviceURL, conOptions)
return &ServiceClient{
client: newServiceClient(conn.Endpoint(), conn.Pipeline()),
sharedKey: cred,
}, nil
}
// NewServiceClientFromConnectionString creates a service client from the given connection string.
//nolint
func NewServiceClientFromConnectionString(connectionString string, options *ClientOptions) (*ServiceClient, error) {
endpoint, credential, err := parseConnectionString(connectionString)
if err != nil {
return nil, err
}
return NewServiceClientWithSharedKey(endpoint, credential, options)
}
//NewServiceClientWithUserDelegationCredential obtains a UserDelegationKey object using the base ServiceURL object.
//OAuth is required for this call, as well as any role that can delegate access to the storage account.
func (s *ServiceClient) NewServiceClientWithUserDelegationCredential(ctx context.Context, info KeyInfo, timeout *int32, requestID *string) (UserDelegationCredential, error) {
options := serviceClientGetUserDelegationKeyOptions{
RequestID: requestID,
Timeout: timeout,
}
sc := newServiceClient(s.client.endpoint, s.client.pl)
udk, err := sc.GetUserDelegationKey(ctx, info, &options)
if err != nil {
return UserDelegationCredential{}, err
}
return *NewUserDelegationCredential(strings.Split(s.client.endpoint, ".")[0], udk.UserDelegationKey), nil
}
// NewContainerClient creates a new ContainerClient object by concatenating containerName to the end of
// ServiceClient's URL. The new ContainerClient uses the same request policy pipeline as the ServiceClient.
// To change the pipeline, create the ContainerClient and then call its WithPipeline method passing in the
// desired pipeline object. Or, call this package's NewContainerClient instead of calling this object's
// NewContainerClient method.
func (s *ServiceClient) NewContainerClient(containerName string) (*ContainerClient, error) {
containerURL := appendToURLPath(s.client.endpoint, containerName)
return &ContainerClient{
client: newContainerClient(containerURL, s.client.pl),
sharedKey: s.sharedKey,
}, nil
}
// CreateContainer is a lifecycle method to creates a new container under the specified account.
// If the container with the same name already exists, a ResourceExistsError will be raised.
// This method returns a client with which to interact with the newly created container.
func (s *ServiceClient) CreateContainer(ctx context.Context, containerName string, options *ContainerCreateOptions) (ContainerCreateResponse, error) {
containerClient, err := s.NewContainerClient(containerName)
if err != nil {
return ContainerCreateResponse{}, err
}
containerCreateResp, err := containerClient.Create(ctx, options)
return containerCreateResp, err
}
// DeleteContainer is a lifecycle method that marks the specified container for deletion.
// The container and any blobs contained within it are later deleted during garbage collection.
// If the container is not found, a ResourceNotFoundError will be raised.
func (s *ServiceClient) DeleteContainer(ctx context.Context, containerName string, options *ContainerDeleteOptions) (ContainerDeleteResponse, error) {
containerClient, _ := s.NewContainerClient(containerName)
containerDeleteResp, err := containerClient.Delete(ctx, options)
return containerDeleteResp, err
}
// appendToURLPath appends a string to the end of a URL's path (prefixing the string with a '/' if required)
func appendToURLPath(u string, name string) string {
// e.g. "https://ms.com/a/b/?k1=v1&k2=v2#f"
// When you call url.Parse() this is what you'll get:
// Scheme: "https"
// Opaque: ""
// User: nil
// Host: "ms.com"
// Path: "/a/b/" This should start with a / and it might or might not have a trailing slash
// RawPath: ""
// ForceQuery: false
// RawQuery: "k1=v1&k2=v2"
// Fragment: "f"
uri, _ := url.Parse(u)
if len(uri.Path) == 0 || uri.Path[len(uri.Path)-1] != '/' {
uri.Path += "/" // Append "/" to end before appending name
}
uri.Path += name
return uri.String()
}
// GetAccountInfo provides account level information
func (s *ServiceClient) GetAccountInfo(ctx context.Context, o *ServiceGetAccountInfoOptions) (ServiceGetAccountInfoResponse, error) {
getAccountInfoOptions := o.format()
resp, err := s.client.GetAccountInfo(ctx, getAccountInfoOptions)
return toServiceGetAccountInfoResponse(resp), handleError(err)
}
// ListContainers operation returns a pager of the containers under the specified account.
// Use an empty Marker to start enumeration from the beginning. Container names are returned in lexicographic order.
// For more information, see https://docs.microsoft.com/rest/api/storageservices/list-containers2.
func (s *ServiceClient) ListContainers(o *ListContainersOptions) *ServiceListContainersSegmentPager {
listOptions := o.format()
pager := s.client.ListContainersSegment(listOptions)
//TODO: .Err()?
//// override the generated advancer, which is incorrect
//if pager.Err() != nil {
// return pager
//}
pager.advancer = func(ctx context.Context, response serviceClientListContainersSegmentResponse) (*policy.Request, error) {
if response.ListContainersSegmentResponse.NextMarker == nil {
return nil, handleError(errors.New("unexpected missing NextMarker"))
}
req, err := s.client.listContainersSegmentCreateRequest(ctx, listOptions)
if err != nil {
return nil, handleError(err)
}
queryValues, _ := url.ParseQuery(req.Raw().URL.RawQuery)
queryValues.Set("marker", *response.ListContainersSegmentResponse.NextMarker)
req.Raw().URL.RawQuery = queryValues.Encode()
return req, nil
}
return toServiceListContainersSegmentPager(*pager)
}
// GetProperties - gets the properties of a storage account's Blob service, including properties for Storage Analytics
// and CORS (Cross-Origin Resource Sharing) rules.
func (s *ServiceClient) GetProperties(ctx context.Context, o *ServiceGetPropertiesOptions) (ServiceGetPropertiesResponse, error) {
getPropertiesOptions := o.format()
resp, err := s.client.GetProperties(ctx, getPropertiesOptions)
return toServiceGetPropertiesResponse(resp), handleError(err)
}
// SetProperties Sets the properties of a storage account's Blob service, including Azure Storage Analytics.
// If an element (e.g. analytics_logging) is left as None, the existing settings on the service for that functionality are preserved.
func (s *ServiceClient) SetProperties(ctx context.Context, o *ServiceSetPropertiesOptions) (ServiceSetPropertiesResponse, error) {
properties, setPropertiesOptions := o.format()
resp, err := s.client.SetProperties(ctx, properties, setPropertiesOptions)
return toServiceSetPropertiesResponse(resp), handleError(err)
}
// GetStatistics Retrieves statistics related to replication for the Blob service.
// It is only available when read-access geo-redundant replication is enabled for the storage account.
// With geo-redundant replication, Azure Storage maintains your data durable
// in two locations. In both locations, Azure Storage constantly maintains
// multiple healthy replicas of your data. The location where you read,
// create, update, or delete data is the primary storage account location.
// The primary location exists in the region you choose at the time you
// create an account via the Azure Management Azure classic portal, for
// example, North Central US. The location to which your data is replicated
// is the secondary location. The secondary location is automatically
// determined based on the location of the primary; it is in a second data
// center that resides in the same region as the primary location. Read-only
// access is available from the secondary location, if read-access geo-redundant
// replication is enabled for your storage account.
func (s *ServiceClient) GetStatistics(ctx context.Context, o *ServiceGetStatisticsOptions) (ServiceGetStatisticsResponse, error) {
getStatisticsOptions := o.format()
resp, err := s.client.GetStatistics(ctx, getStatisticsOptions)
return toServiceGetStatisticsResponse(resp), handleError(err)
}
// CanGetAccountSASToken checks if shared key in ServiceClient is nil
func (s *ServiceClient) CanGetAccountSASToken() bool {
return s.sharedKey != nil
}
// GetSASURL is a convenience method for generating a SAS token for the currently pointed at account.
// It can only be used if the credential supplied during creation was a SharedKeyCredential.
// This validity can be checked with CanGetAccountSASToken().
func (s *ServiceClient) GetSASURL(resources AccountSASResourceTypes, permissions AccountSASPermissions, start time.Time, expiry time.Time) (string, error) {
if s.sharedKey == nil {
return "", errors.New("SAS can only be signed with a SharedKeyCredential")
}
qps, err := AccountSASSignatureValues{
Version: SASVersion,
Protocol: SASProtocolHTTPS,
Permissions: permissions.String(),
Services: "b",
ResourceTypes: resources.String(),
StartTime: start.UTC(),
ExpiryTime: expiry.UTC(),
}.Sign(s.sharedKey)
if err != nil {
return "", err
}
endpoint := s.URL()
if !strings.HasSuffix(endpoint, "/") {
endpoint += "/"
}
endpoint += "?" + qps.Encode()
return endpoint, nil
}
// FindBlobsByTags operation finds all blobs in the storage account whose tags match a given search expression.
// Filter blobs searches across all containers within a storage account but can be scoped within the expression to a single container.
// https://docs.microsoft.com/en-us/rest/api/storageservices/find-blobs-by-tags
// eg. "dog='germanshepherd' and penguin='emperorpenguin'"
// To specify a container, eg. "@container=’containerName’ and Name = ‘C’"
func (s *ServiceClient) FindBlobsByTags(ctx context.Context, o *ServiceFilterBlobsOptions) (ServiceFilterBlobsResponse, error) {
// TODO: Use pager here? Missing support from zz_generated_pagers.go
serviceFilterBlobsOptions := o.pointer()
resp, err := s.client.FilterBlobs(ctx, serviceFilterBlobsOptions)
return toServiceFilterBlobsResponse(resp), err
}