Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azfile: STG 87 #21452

Merged
merged 15 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions sdk/storage/azfile/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# Release History

## 1.0.1 (Unreleased)
## 1.1.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes
* Updated service version to `2022-11-02`.
* Added OAuth support.
* Added [Rename Directory API](https://learn.microsoft.com/rest/api/storageservices/rename-directory).
* Added [Rename File API](https://learn.microsoft.com/rest/api/storageservices/rename-file).
* Added `x-ms-file-change-time` request header in
* Create File/Directory
* Set File/Directory Properties
* Copy File
* Added `x-ms-file-last-write-time` request header in Put Range and Put Range from URL.
* Updated the SAS Version to `2022-11-02` and added `Encryption Scope` to Account SAS.
* Trailing dot support for files and directories.

### Bugs Fixed

* Fixed service SAS creation where expiry time or permissions can be omitted when stored access policy is used.
* Fixed issue where some requests fail with mismatch in string to sign.

### Other Changes

* Updated version of azcore to 1.7.1 and azidentity to 1.3.1.
* Added `dragonfly` and `aix` to build constraints in `mmf_unix.go`.

## 1.0.0 (2023-07-12)
Expand Down
24 changes: 19 additions & 5 deletions sdk/storage/azfile/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Azure File Storage SDK for Go

> Service Version: 2020-10-02
> Service Version: 2022-11-02

Azure File Shares offers fully managed file shares in the cloud that are accessible via the industry standard
[Server Message Block (SMB) protocol](https://docs.microsoft.com/windows/desktop/FileIO/microsoft-smb-protocol-and-cifs-protocol-overview).
Expand All @@ -19,6 +19,12 @@ Install the Azure File Storage SDK for Go with [go get][goget]:
go get github.com/Azure/azure-sdk-for-go/sdk/storage/azfile
```

If you plan to authenticate with Azure Active Directory (recommended), also install the [azidentity][azidentity] module.

```Powershell
go get github.com/Azure/azure-sdk-for-go/sdk/azidentity
```

### Prerequisites

A supported [Go][godevdl] version (the Azure SDK supports the two most recent Go releases).
Expand All @@ -39,18 +45,24 @@ az storage account create --name MyStorageAccount --resource-group MyResourceGro
The Azure File Storage SDK for Go allows you to interact with four types of resources: the storage
account itself, file shares, directories, and files. Interaction with these resources starts with an instance of a
client. To create a client object, you will need the storage account's file service URL and a
credential that allows you to access the storage account:
credential that allows you to access the storage account. The [azidentity][azidentity] module makes it easy to add
Azure Active Directory support for authenticating Azure SDK clients with their corresponding Azure services.

```go
// create a credential for authenticating using shared key
cred, err := service.NewSharedKeyCredential("<my-storage-account-name>", "<my-storage-account-key>")
// create a credential for authenticating with Azure Active Directory
cred, err := azidentity.NewDefaultAzureCredential(nil)
// TODO: handle err

// create service.Client for the specified storage account that uses the above credential
client, err := service.NewClientWithSharedKeyCredential("https://<my-storage-account-name>.file.core.windows.net/", cred, nil)
client, err := service.NewClient("https://<my-storage-account-name>.file.core.windows.net/", cred, &service.ClientOptions{FileRequestIntent: to.Ptr(service.ShareTokenIntentBackup)})
// TODO: handle err
```

Learn more about enabling Azure Active Directory for authentication with Azure Storage: [Authorize access to blobs using Azure Active Directory][storage_ad]

Other options for authentication include connection strings, shared key, and shared access signatures (SAS).
Use the appropriate client constructor function for the authentication mechanism you wish to use.

## Key concepts

Azure file shares can be used to:
Expand Down Expand Up @@ -264,3 +276,5 @@ additional questions or comments.
[coc]: https://opensource.microsoft.com/codeofconduct/
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
[coc_contact]: mailto:[email protected]
[azidentity]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity
[storage_ad]: https://learn.microsoft.com/azure/storage/common/storage-auth-aad
2 changes: 1 addition & 1 deletion sdk/storage/azfile/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "go",
"TagPrefix": "go/storage/azfile",
"Tag": "go/storage/azfile_600a15563c"
"Tag": "go/storage/azfile_66b915bf67"
}
105 changes: 93 additions & 12 deletions sdk/storage/azfile/directory/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ package directory

import (
"context"
"errors"
"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"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/file"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/base"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/generated"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/sas"
"net/http"
"net/url"
"strings"
Expand All @@ -26,15 +29,43 @@ type ClientOptions base.ClientOptions
// Client represents a URL to the Azure Storage directory allowing you to manipulate its directories and files.
type Client base.Client[generated.DirectoryClient]

// NewClient creates an instance of Client with the specified values.
// - directoryURL - the URL of the directory e.g. https://<account>.file.core.windows.net/share/directory
// - cred - an Azure AD credential, typically obtained via the azidentity module
// - options - client options; pass nil to accept the default values
//
// Note that ClientOptions.FileRequestIntent is currently required for token authentication.
func NewClient(directoryURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
authPolicy := runtime.NewBearerTokenPolicy(cred, []string{shared.TokenScope}, nil)
conOptions := shared.GetClientOptions(options)
plOpts := runtime.PipelineOptions{
PerRetry: []policy.Policy{authPolicy},
}
base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts)

azClient, err := azcore.NewClient(shared.DirectoryClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)
if err != nil {
return nil, err
}

return (*Client)(base.NewDirectoryClient(directoryURL, azClient, nil, (*base.ClientOptions)(conOptions))), nil
}

// NewClientWithNoCredential creates an instance of Client with the specified values.
// This is used to anonymously access a directory or with a shared access signature (SAS) token.
// - directoryURL - the URL of the directory e.g. https://<account>.file.core.windows.net/share/directory?<sas token>
// - options - client options; pass nil to accept the default values
func NewClientWithNoCredential(directoryURL string, options *ClientOptions) (*Client, error) {
conOptions := shared.GetClientOptions(options)
pl := runtime.NewPipeline(exported.ModuleName, exported.ModuleVersion, runtime.PipelineOptions{}, &conOptions.ClientOptions)
plOpts := runtime.PipelineOptions{}
base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts)

azClient, err := azcore.NewClient(shared.DirectoryClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)
if err != nil {
return nil, err
}

return (*Client)(base.NewDirectoryClient(directoryURL, pl, nil)), nil
return (*Client)(base.NewDirectoryClient(directoryURL, azClient, nil, (*base.ClientOptions)(conOptions))), nil
}

// NewClientWithSharedKeyCredential creates an instance of Client with the specified values.
Expand All @@ -44,10 +75,17 @@ func NewClientWithNoCredential(directoryURL string, options *ClientOptions) (*Cl
func NewClientWithSharedKeyCredential(directoryURL string, cred *SharedKeyCredential, options *ClientOptions) (*Client, error) {
authPolicy := exported.NewSharedKeyCredPolicy(cred)
conOptions := shared.GetClientOptions(options)
conOptions.PerRetryPolicies = append(conOptions.PerRetryPolicies, authPolicy)
pl := runtime.NewPipeline(exported.ModuleName, exported.ModuleVersion, runtime.PipelineOptions{}, &conOptions.ClientOptions)
plOpts := runtime.PipelineOptions{
PerRetry: []policy.Policy{authPolicy},
}
base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts)

return (*Client)(base.NewDirectoryClient(directoryURL, pl, cred)), nil
azClient, err := azcore.NewClient(shared.DirectoryClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)
if err != nil {
return nil, err
}

return (*Client)(base.NewDirectoryClient(directoryURL, azClient, cred, (*base.ClientOptions)(conOptions))), nil
}

// NewClientFromConnectionString creates an instance of Client with the specified values.
Expand Down Expand Up @@ -83,6 +121,10 @@ func (d *Client) sharedKey() *SharedKeyCredential {
return base.SharedKey((*base.Client[generated.DirectoryClient])(d))
}

func (d *Client) getClientOptions() *base.ClientOptions {
return base.GetClientOptions((*base.Client[generated.DirectoryClient])(d))
}

// URL returns the URL endpoint used by the Client object.
func (d *Client) URL() string {
return d.generated().Endpoint()
Expand All @@ -93,23 +135,23 @@ func (d *Client) URL() string {
func (d *Client) NewSubdirectoryClient(subDirectoryName string) *Client {
subDirectoryName = url.PathEscape(strings.TrimRight(subDirectoryName, "/"))
subDirectoryURL := runtime.JoinPaths(d.URL(), subDirectoryName)
return (*Client)(base.NewDirectoryClient(subDirectoryURL, d.generated().Pipeline(), d.sharedKey()))
return (*Client)(base.NewDirectoryClient(subDirectoryURL, d.generated().InternalClient(), d.sharedKey(), d.getClientOptions()))
}

// NewFileClient creates a new file.Client object by concatenating fileName to the end of this Client's URL.
// The new file.Client uses the same request policy pipeline as the Client.
func (d *Client) NewFileClient(fileName string) *file.Client {
fileName = url.PathEscape(fileName)
fileURL := runtime.JoinPaths(d.URL(), fileName)
return (*file.Client)(base.NewFileClient(fileURL, d.generated().Pipeline(), d.sharedKey()))
return (*file.Client)(base.NewFileClient(fileURL, d.generated().InternalClient().WithClientName(shared.FileClient), d.sharedKey(), d.getClientOptions()))
}

// Create operation creates a new directory under the specified share or parent directory.
// file.ParseNTFSFileAttributes method can be used to convert the file attributes returned in response to NTFSFileAttributes.
// For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/create-directory.
func (d *Client) Create(ctx context.Context, options *CreateOptions) (CreateResponse, error) {
fileAttributes, fileCreationTime, fileLastWriteTime, opts := options.format()
resp, err := d.generated().Create(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, opts)
opts := options.format()
resp, err := d.generated().Create(ctx, opts)
return resp, err
}

Expand All @@ -122,6 +164,45 @@ func (d *Client) Delete(ctx context.Context, options *DeleteOptions) (DeleteResp
return resp, err
}

// Rename operation renames a directory, and can optionally set system properties for the directory.
// - destinationPath: the destination path to rename the directory to.
//
// For more information, see https://learn.microsoft.com/rest/api/storageservices/rename-directory.
func (d *Client) Rename(ctx context.Context, destinationPath string, options *RenameOptions) (RenameResponse, error) {
destinationPath = strings.Trim(strings.TrimSpace(destinationPath), "/")
if len(destinationPath) == 0 {
return RenameResponse{}, errors.New("destination path must not be empty")
}

opts, destLease, smbInfo := options.format()

urlParts, err := sas.ParseURL(d.URL())
if err != nil {
return RenameResponse{}, err
}

destParts := strings.Split(destinationPath, "?")
newDestPath := destParts[0]
newDestQuery := ""
if len(destParts) == 2 {
newDestQuery = destParts[1]
}

urlParts.DirectoryOrFilePath = newDestPath
destURL := urlParts.String()
// replace the query part if it is present in destination path
if len(newDestQuery) > 0 {
destURL = strings.Split(destURL, "?")[0] + "?" + newDestQuery
}

destDirClient := (*Client)(base.NewDirectoryClient(destURL, d.generated().InternalClient(), d.sharedKey(), d.getClientOptions()))

resp, err := destDirClient.generated().Rename(ctx, d.URL(), opts, nil, destLease, smbInfo)
return RenameResponse{
DirectoryClientRenameResponse: resp,
}, err
}

// GetProperties operation returns all system properties for the specified directory, and it can also be used to check the existence of a directory.
// file.ParseNTFSFileAttributes method can be used to convert the file attributes returned in response to NTFSFileAttributes.
// For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/get-directory-properties.
Expand All @@ -135,8 +216,8 @@ func (d *Client) GetProperties(ctx context.Context, options *GetPropertiesOption
// file.ParseNTFSFileAttributes method can be used to convert the file attributes returned in response to NTFSFileAttributes.
// For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/set-directory-properties.
func (d *Client) SetProperties(ctx context.Context, options *SetPropertiesOptions) (SetPropertiesResponse, error) {
fileAttributes, fileCreationTime, fileLastWriteTime, opts := options.format()
resp, err := d.generated().SetProperties(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, opts)
opts := options.format()
resp, err := d.generated().SetProperties(ctx, opts)
return resp, err
}

Expand Down Expand Up @@ -195,7 +276,7 @@ func (d *Client) NewListFilesAndDirectoriesPager(options *ListFilesAndDirectorie
if err != nil {
return ListFilesAndDirectoriesResponse{}, err
}
resp, err := d.generated().Pipeline().Do(req)
resp, err := d.generated().InternalClient().Pipeline().Do(req)
if err != nil {
return ListFilesAndDirectoriesResponse{}, err
}
Expand Down
Loading