1package azblob
2
3import (
4	"context"
5	"net/url"
6	"strings"
7
8	"github.com/Azure/azure-pipeline-go/pipeline"
9)
10
11const (
12	// ContainerNameRoot is the special Azure Storage name used to identify a storage account's root container.
13	ContainerNameRoot = "$root"
14
15	// ContainerNameLogs is the special Azure Storage name used to identify a storage account's logs container.
16	ContainerNameLogs = "$logs"
17)
18
19// A ServiceURL represents a URL to the Azure Storage Blob service allowing you to manipulate blob containers.
20type ServiceURL struct {
21	client serviceClient
22}
23
24// NewServiceURL creates a ServiceURL object using the specified URL and request policy pipeline.
25func NewServiceURL(primaryURL url.URL, p pipeline.Pipeline) ServiceURL {
26	client := newServiceClient(primaryURL, p)
27	return ServiceURL{client: client}
28}
29
30//GetUserDelegationCredential obtains a UserDelegationKey object using the base ServiceURL object.
31//OAuth is required for this call, as well as any role that can delegate access to the storage account.
32func (s ServiceURL) GetUserDelegationCredential(ctx context.Context, info KeyInfo, timeout *int32, requestID *string) (UserDelegationCredential, error) {
33	sc := newServiceClient(s.client.url, s.client.p)
34	udk, err := sc.GetUserDelegationKey(ctx, info, timeout, requestID)
35	if err != nil {
36		return UserDelegationCredential{}, err
37	}
38	return NewUserDelegationCredential(strings.Split(s.client.url.Host, ".")[0], *udk), nil
39}
40
41// URL returns the URL endpoint used by the ServiceURL object.
42func (s ServiceURL) URL() url.URL {
43	return s.client.URL()
44}
45
46// String returns the URL as a string.
47func (s ServiceURL) String() string {
48	u := s.URL()
49	return u.String()
50}
51
52// WithPipeline creates a new ServiceURL object identical to the source but with the specified request policy pipeline.
53func (s ServiceURL) WithPipeline(p pipeline.Pipeline) ServiceURL {
54	return NewServiceURL(s.URL(), p)
55}
56
57// NewContainerURL creates a new ContainerURL object by concatenating containerName to the end of
58// ServiceURL's URL. The new ContainerURL uses the same request policy pipeline as the ServiceURL.
59// To change the pipeline, create the ContainerURL and then call its WithPipeline method passing in the
60// desired pipeline object. Or, call this package's NewContainerURL instead of calling this object's
61// NewContainerURL method.
62func (s ServiceURL) NewContainerURL(containerName string) ContainerURL {
63	containerURL := appendToURLPath(s.URL(), containerName)
64	return NewContainerURL(containerURL, s.client.Pipeline())
65}
66
67// appendToURLPath appends a string to the end of a URL's path (prefixing the string with a '/' if required)
68func appendToURLPath(u url.URL, name string) url.URL {
69	// e.g. "https://ms.com/a/b/?k1=v1&k2=v2#f"
70	// When you call url.Parse() this is what you'll get:
71	//     Scheme: "https"
72	//     Opaque: ""
73	//       User: nil
74	//       Host: "ms.com"
75	//       Path: "/a/b/"	This should start with a / and it might or might not have a trailing slash
76	//    RawPath: ""
77	// ForceQuery: false
78	//   RawQuery: "k1=v1&k2=v2"
79	//   Fragment: "f"
80	if len(u.Path) == 0 || u.Path[len(u.Path)-1] != '/' {
81		u.Path += "/" // Append "/" to end before appending name
82	}
83	u.Path += name
84	return u
85}
86
87// ListContainersFlatSegment returns a single segment of containers starting from the specified Marker. Use an empty
88// Marker to start enumeration from the beginning. Container names are returned in lexicographic order.
89// After getting a segment, process it, and then call ListContainersFlatSegment again (passing the the
90// previously-returned Marker) to get the next segment. For more information, see
91// https://docs.microsoft.com/rest/api/storageservices/list-containers2.
92func (s ServiceURL) ListContainersSegment(ctx context.Context, marker Marker, o ListContainersSegmentOptions) (*ListContainersSegmentResponse, error) {
93	prefix, include, maxResults := o.pointers()
94	return s.client.ListContainersSegment(ctx, prefix, marker.Val, maxResults, include, nil, nil)
95}
96
97// ListContainersOptions defines options available when calling ListContainers.
98type ListContainersSegmentOptions struct {
99	Detail     ListContainersDetail // No IncludeType header is produced if ""
100	Prefix     string               // No Prefix header is produced if ""
101	MaxResults int32                // 0 means unspecified
102	// TODO: update swagger to generate this type?
103}
104
105func (o *ListContainersSegmentOptions) pointers() (prefix *string, include ListContainersIncludeType, maxResults *int32) {
106	if o.Prefix != "" {
107		prefix = &o.Prefix
108	}
109	if o.MaxResults != 0 {
110		maxResults = &o.MaxResults
111	}
112	include = ListContainersIncludeType(o.Detail.string())
113	return
114}
115
116// ListContainersFlatDetail indicates what additional information the service should return with each container.
117type ListContainersDetail struct {
118	// Tells the service whether to return metadata for each container.
119	Metadata bool
120}
121
122// string produces the Include query parameter's value.
123func (d *ListContainersDetail) string() string {
124	items := make([]string, 0, 1)
125	// NOTE: Multiple strings MUST be appended in alphabetic order or signing the string for authentication fails!
126	if d.Metadata {
127		items = append(items, string(ListContainersIncludeMetadata))
128	}
129	if len(items) > 0 {
130		return strings.Join(items, ",")
131	}
132	return string(ListContainersIncludeNone)
133}
134
135func (bsu ServiceURL) GetProperties(ctx context.Context) (*StorageServiceProperties, error) {
136	return bsu.client.GetProperties(ctx, nil, nil)
137}
138
139func (bsu ServiceURL) SetProperties(ctx context.Context, properties StorageServiceProperties) (*ServiceSetPropertiesResponse, error) {
140	return bsu.client.SetProperties(ctx, properties, nil, nil)
141}
142
143func (bsu ServiceURL) GetStatistics(ctx context.Context) (*StorageServiceStats, error) {
144	return bsu.client.GetStatistics(ctx, nil, nil)
145}
146