diff --git a/sdk/storage/azfile/directory/client.go b/sdk/storage/azfile/directory/client.go index e01e7d0c960e..f4d5c878c8dd 100644 --- a/sdk/storage/azfile/directory/client.go +++ b/sdk/storage/azfile/directory/client.go @@ -9,10 +9,19 @@ package directory import ( "context" "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/fileerror" "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" + "time" ) // ClientOptions contains the optional parameters when creating a Client. @@ -28,7 +37,10 @@ type Client base.Client[generated.DirectoryClient] // - directoryURL - the URL of the directory e.g. https://.file.core.windows.net/share/directory? // - options - client options; pass nil to accept the default values func NewClientWithNoCredential(directoryURL string, options *ClientOptions) (*Client, error) { - return nil, nil + conOptions := shared.GetClientOptions(options) + pl := runtime.NewPipeline(exported.ModuleName, exported.ModuleVersion, runtime.PipelineOptions{}, &conOptions.ClientOptions) + + return (*Client)(base.NewDirectoryClient(directoryURL, pl, nil)), nil } // NewClientWithSharedKeyCredential creates an instance of Client with the specified values. @@ -36,7 +48,12 @@ func NewClientWithNoCredential(directoryURL string, options *ClientOptions) (*Cl // - cred - a SharedKeyCredential created with the matching directory's storage account and access key // - options - client options; pass nil to accept the default values func NewClientWithSharedKeyCredential(directoryURL string, cred *SharedKeyCredential, options *ClientOptions) (*Client, error) { - return nil, nil + 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) + + return (*Client)(base.NewDirectoryClient(directoryURL, pl, cred)), nil } // NewClientFromConnectionString creates an instance of Client with the specified values. @@ -45,7 +62,23 @@ func NewClientWithSharedKeyCredential(directoryURL string, cred *SharedKeyCreden // - directoryPath - the path of the directory within the share // - options - client options; pass nil to accept the default values func NewClientFromConnectionString(connectionString string, shareName string, directoryPath string, options *ClientOptions) (*Client, error) { - return nil, nil + parsed, err := shared.ParseConnectionString(connectionString) + if err != nil { + return nil, err + } + + directoryPath = strings.ReplaceAll(directoryPath, "\\", "/") + parsed.ServiceURL = runtime.JoinPaths(parsed.ServiceURL, shareName, directoryPath) + + if parsed.AccountKey != "" && parsed.AccountName != "" { + credential, err := exported.NewSharedKeyCredential(parsed.AccountName, parsed.AccountKey) + if err != nil { + return nil, err + } + return NewClientWithSharedKeyCredential(parsed.ServiceURL, credential, options) + } + + return NewClientWithNoCredential(parsed.ServiceURL, options) } func (d *Client) generated() *generated.DirectoryClient { @@ -64,48 +97,146 @@ func (d *Client) URL() string { // NewSubdirectoryClient creates a new Client object by concatenating subDirectoryName to the end of this Client's URL. // The new subdirectory Client uses the same request policy pipeline as the parent directory Client. func (d *Client) NewSubdirectoryClient(subDirectoryName string) *Client { - return nil + subDirectoryName = url.PathEscape(subDirectoryName) + subDirectoryURL := runtime.JoinPaths(d.URL(), subDirectoryName) + return (*Client)(base.NewDirectoryClient(subDirectoryURL, d.generated().Pipeline(), d.sharedKey())) } // 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 { - return nil + fileName = url.PathEscape(fileName) + fileURL := runtime.JoinPaths(d.URL(), fileName) + return (*file.Client)(base.NewFileClient(fileURL, d.generated().Pipeline(), d.sharedKey())) } // Create operation creates a new directory under the specified share or parent directory. // 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) { - return CreateResponse{}, nil + fileAttributes, fileCreationTime, fileLastWriteTime, opts := options.format() + resp, err := d.generated().Create(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, opts) + return resp, err } // Delete operation removes the specified empty directory. Note that the directory must be empty before it can be deleted. // Deleting directories that aren't empty returns error 409 (Directory Not Empty). // For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/delete-directory. func (d *Client) Delete(ctx context.Context, options *DeleteOptions) (DeleteResponse, error) { - return DeleteResponse{}, nil + opts := options.format() + resp, err := d.generated().Delete(ctx, opts) + return 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. // For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/get-directory-properties. func (d *Client) GetProperties(ctx context.Context, options *GetPropertiesOptions) (GetPropertiesResponse, error) { - return GetPropertiesResponse{}, nil + opts := options.format() + resp, err := d.generated().GetProperties(ctx, opts) + return resp, err } // SetProperties operation sets system properties for the specified directory. // 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) { - return SetPropertiesResponse{}, nil + fileAttributes, fileCreationTime, fileLastWriteTime, opts := options.format() + resp, err := d.generated().SetProperties(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, opts) + return resp, err } // SetMetadata operation sets user-defined metadata for the specified directory. // For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/set-directory-metadata. func (d *Client) SetMetadata(ctx context.Context, options *SetMetadataOptions) (SetMetadataResponse, error) { - return SetMetadataResponse{}, nil + opts := options.format() + resp, err := d.generated().SetMetadata(ctx, opts) + return resp, err +} + +// ForceCloseHandles operation closes a handle or handles opened on a directory. +// - handleID - Specifies the handle ID to be closed. Use an asterisk (*) as a wildcard string to specify all handles. +// +// For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/force-close-handles. +func (d *Client) ForceCloseHandles(ctx context.Context, handleID string, options *ForceCloseHandlesOptions) (ForceCloseHandlesResponse, error) { + opts := options.format() + resp, err := d.generated().ForceCloseHandles(ctx, handleID, opts) + return resp, err +} + +// ListHandles operation returns a list of open handles on a directory. +// For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/list-handles. +func (d *Client) ListHandles(ctx context.Context, options *ListHandlesOptions) (ListHandlesResponse, error) { + opts := options.format() + resp, err := d.generated().ListHandles(ctx, opts) + return resp, err } // NewListFilesAndDirectoriesPager operation returns a pager for the files and directories starting from the specified Marker. // For more information, see https://learn.microsoft.com/en-us/rest/api/storageservices/list-directories-and-files. func (d *Client) NewListFilesAndDirectoriesPager(options *ListFilesAndDirectoriesOptions) *runtime.Pager[ListFilesAndDirectoriesResponse] { - return nil + listOptions := generated.DirectoryClientListFilesAndDirectoriesSegmentOptions{} + if options != nil { + listOptions.Include = options.Include.format() + listOptions.IncludeExtendedInfo = options.IncludeExtendedInfo + listOptions.Marker = options.Marker + listOptions.Maxresults = options.MaxResults + listOptions.Prefix = options.Prefix + listOptions.Sharesnapshot = options.ShareSnapshot + } + + return runtime.NewPager(runtime.PagingHandler[ListFilesAndDirectoriesResponse]{ + More: func(page ListFilesAndDirectoriesResponse) bool { + return page.NextMarker != nil && len(*page.NextMarker) > 0 + }, + Fetcher: func(ctx context.Context, page *ListFilesAndDirectoriesResponse) (ListFilesAndDirectoriesResponse, error) { + var req *policy.Request + var err error + if page == nil { + req, err = d.generated().ListFilesAndDirectoriesSegmentCreateRequest(ctx, &listOptions) + } else { + listOptions.Marker = page.NextMarker + req, err = d.generated().ListFilesAndDirectoriesSegmentCreateRequest(ctx, &listOptions) + } + if err != nil { + return ListFilesAndDirectoriesResponse{}, err + } + resp, err := d.generated().Pipeline().Do(req) + if err != nil { + return ListFilesAndDirectoriesResponse{}, err + } + if !runtime.HasStatusCode(resp, http.StatusOK) { + return ListFilesAndDirectoriesResponse{}, runtime.NewResponseError(resp) + } + return d.generated().ListFilesAndDirectoriesSegmentHandleResponse(resp) + }, + }) +} + +// GetSASURL is a convenience method for generating a SAS token for the currently pointed at directory. +// It can only be used if the credential supplied during creation was a SharedKeyCredential. +func (d *Client) GetSASURL(permissions sas.FilePermissions, expiry time.Time, o *GetSASURLOptions) (string, error) { + if d.sharedKey() == nil { + return "", fileerror.MissingSharedKeyCredential + } + st := o.format() + + urlParts, err := sas.ParseURL(d.URL()) + if err != nil { + return "", err + } + + qps, err := sas.SignatureValues{ + Version: sas.Version, + Protocol: sas.ProtocolHTTPS, + ShareName: urlParts.ShareName, + DirectoryOrFilePath: urlParts.DirectoryOrFilePath, + Permissions: permissions.String(), + StartTime: st, + ExpiryTime: expiry.UTC(), + }.SignWithSharedKey(d.sharedKey()) + if err != nil { + return "", err + } + + endpoint := d.URL() + "?" + qps.Encode() + + return endpoint, nil } diff --git a/sdk/storage/azfile/directory/client_test.go b/sdk/storage/azfile/directory/client_test.go new file mode 100644 index 000000000000..bdd6b71cfb29 --- /dev/null +++ b/sdk/storage/azfile/directory/client_test.go @@ -0,0 +1,769 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package directory_test + +import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/directory" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/file" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/fileerror" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/testcommon" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/sas" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/service" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "testing" + "time" +) + +func Test(t *testing.T) { + recordMode := recording.GetRecordMode() + t.Logf("Running directory Tests in %s mode\n", recordMode) + if recordMode == recording.LiveMode { + suite.Run(t, &DirectoryRecordedTestsSuite{}) + suite.Run(t, &DirectoryUnrecordedTestsSuite{}) + } else if recordMode == recording.PlaybackMode { + suite.Run(t, &DirectoryRecordedTestsSuite{}) + } else if recordMode == recording.RecordingMode { + suite.Run(t, &DirectoryRecordedTestsSuite{}) + } +} + +func (d *DirectoryRecordedTestsSuite) BeforeTest(suite string, test string) { + testcommon.BeforeTest(d.T(), suite, test) +} + +func (d *DirectoryRecordedTestsSuite) AfterTest(suite string, test string) { + testcommon.AfterTest(d.T(), suite, test) +} + +func (d *DirectoryUnrecordedTestsSuite) BeforeTest(suite string, test string) { + +} + +func (d *DirectoryUnrecordedTestsSuite) AfterTest(suite string, test string) { + +} + +type DirectoryRecordedTestsSuite struct { + suite.Suite +} + +type DirectoryUnrecordedTestsSuite struct { + suite.Suite +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirNewDirectoryClient() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, err := testcommon.GetRequiredEnv(testcommon.AccountNameEnvVar) + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := svcClient.NewShareClient(shareName) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := shareClient.NewDirectoryClient(dirName) + + subDirName := "inner" + dirName + subDirClient := dirClient.NewSubdirectoryClient(subDirName) + + correctURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + "/" + subDirName + _require.Equal(subDirClient.URL(), correctURL) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateFileURL() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, err := testcommon.GetRequiredEnv(testcommon.AccountNameEnvVar) + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := svcClient.NewShareClient(shareName) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := shareClient.NewDirectoryClient(dirName) + + fileName := testcommon.GenerateFileName(testName) + fileClient := dirClient.NewFileClient(fileName) + + correctURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + "/" + fileName + _require.Equal(fileClient.URL(), correctURL) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirectoryCreateUsingSharedKey() { + _require := require.New(d.T()) + testName := d.T().Name() + + cred, err := testcommon.GetGenericSharedKeyCredential(testcommon.TestAccountDefault) + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + cred.AccountName() + ".file.core.windows.net/" + shareName + "/" + dirName + dirClient, err := directory.NewClientWithSharedKeyCredential(dirURL, cred, nil) + _require.NoError(err) + + resp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(resp.ETag) + _require.NotNil(resp.RequestID) + _require.Equal(resp.LastModified.IsZero(), false) + _require.Equal(resp.FileCreationTime.IsZero(), false) + _require.Equal(resp.FileLastWriteTime.IsZero(), false) + _require.Equal(resp.FileChangeTime.IsZero(), false) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirectoryCreateUsingConnectionString() { + _require := require.New(d.T()) + testName := d.T().Name() + + connString, err := testcommon.GetGenericConnectionString(testcommon.TestAccountDefault) + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient, err := directory.NewClientFromConnectionString(*connString, shareName, dirName, nil) + _require.NoError(err) + + resp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(resp.ETag) + _require.NotNil(resp.RequestID) + _require.Equal(resp.LastModified.IsZero(), false) + _require.Equal(resp.FileCreationTime.IsZero(), false) + _require.Equal(resp.FileLastWriteTime.IsZero(), false) + _require.Equal(resp.FileChangeTime.IsZero(), false) + + innerDirName1 := "innerdir1" + dirPath := dirName + "/" + innerDirName1 + dirClient1, err := directory.NewClientFromConnectionString(*connString, shareName, dirPath, nil) + _require.NoError(err) + + resp, err = dirClient1.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(resp.RequestID) + _require.Equal(resp.LastModified.IsZero(), false) + _require.Equal(resp.FileCreationTime.IsZero(), false) + + innerDirName2 := "innerdir2" + // using '\' as path separator between directories + dirPath = dirName + "\\" + innerDirName1 + "\\" + innerDirName2 + dirClient2, err := directory.NewClientFromConnectionString(*connString, shareName, dirPath, nil) + _require.NoError(err) + + resp, err = dirClient2.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(resp.RequestID) + _require.Equal(resp.LastModified.IsZero(), false) + _require.Equal(resp.FileCreationTime.IsZero(), false) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirectoryCreateNegativeMultiLevel() { + _require := require.New(d.T()) + testName := d.T().Name() + + connString, err := testcommon.GetGenericConnectionString(testcommon.TestAccountDefault) + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + // dirPath where parent dir does not exist + dirPath := "a/b/c/d/" + dirName + dirClient, err := directory.NewClientFromConnectionString(*connString, shareName, dirPath, nil) + _require.NoError(err) + + resp, err := dirClient.Create(context.Background(), nil) + _require.Error(err) + _require.Nil(resp.RequestID) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ParentNotFound) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirectoryClientUsingSAS() { + _require := require.New(d.T()) + testName := d.T().Name() + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := shareClient.NewDirectoryClient(dirName) + + _, err = dirClient.Create(context.Background(), nil) + _require.NoError(err) + + permissions := sas.FilePermissions{ + Read: true, + Write: true, + Delete: true, + Create: true, + } + expiry := time.Now().Add(time.Hour) + + dirSASURL, err := dirClient.GetSASURL(permissions, expiry, nil) + _require.NoError(err) + + dirSASClient, err := directory.NewClientWithNoCredential(dirSASURL, nil) + _require.NoError(err) + + _, err = dirSASClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.AuthenticationFailed) + + // TODO: create files using dirSASClient +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateDeleteDefault() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := shareClient.NewDirectoryClient(dirName) + _require.NoError(err) + + cResp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(cResp.RequestID) + _require.NotNil(cResp.ETag) + _require.Equal(cResp.Date.IsZero(), false) + _require.Equal(cResp.LastModified.IsZero(), false) + _require.Equal(cResp.FileCreationTime.IsZero(), false) + _require.Equal(cResp.FileLastWriteTime.IsZero(), false) + _require.Equal(cResp.FileChangeTime.IsZero(), false) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.Date.IsZero(), false) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.Equal(gResp.FileCreationTime.IsZero(), false) + _require.Equal(gResp.FileLastWriteTime.IsZero(), false) + _require.Equal(gResp.FileChangeTime.IsZero(), false) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirSetPropertiesNonDefault() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.GetDirectoryClient(dirName, shareClient) + + cResp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(cResp.FilePermissionKey) + + creationTime := time.Now().Add(5 * time.Minute).Round(time.Microsecond) + lastWriteTime := time.Now().Add(10 * time.Minute).Round(time.Millisecond) + + // Set the custom permissions + sResp, err := dirClient.SetProperties(context.Background(), &directory.SetPropertiesOptions{ + FileSMBProperties: &file.SMBProperties{ + Attributes: &file.NTFSFileAttributes{ + ReadOnly: true, + System: true, + }, + CreationTime: &creationTime, + LastWriteTime: &lastWriteTime, + }, + FilePermissions: &file.Permissions{ + Permission: &testcommon.SampleSDDL, + }, + }) + _require.NoError(err) + _require.NotNil(sResp.FileCreationTime) + _require.NotNil(sResp.FileLastWriteTime) + _require.NotNil(sResp.FilePermissionKey) + _require.NotEqual(*sResp.FilePermissionKey, *cResp.FilePermissionKey) + _require.Equal(*sResp.FileCreationTime, creationTime.UTC()) + _require.Equal(*sResp.FileLastWriteTime, lastWriteTime.UTC()) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.NotNil(gResp.FileCreationTime) + _require.NotNil(gResp.FileLastWriteTime) + _require.NotNil(gResp.FilePermissionKey) + _require.Equal(*gResp.FilePermissionKey, *sResp.FilePermissionKey) + _require.Equal(*gResp.FileCreationTime, *sResp.FileCreationTime) + _require.Equal(*gResp.FileLastWriteTime, *sResp.FileLastWriteTime) + _require.Equal(*gResp.FileAttributes, *sResp.FileAttributes) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateDeleteNonDefault() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.GetDirectoryClient(dirName, shareClient) + + md := map[string]*string{ + "Foo": to.Ptr("FooValuE"), + "Bar": to.Ptr("bArvaLue"), + } + + cResp, err := dirClient.Create(context.Background(), &directory.CreateOptions{ + Metadata: md, + FileSMBProperties: &file.SMBProperties{ + Attributes: &file.NTFSFileAttributes{None: true}, + CreationTime: to.Ptr(time.Now().Add(5 * time.Minute)), + LastWriteTime: to.Ptr(time.Now().Add(10 * time.Minute)), + }, + FilePermissions: &file.Permissions{ + Permission: &testcommon.SampleSDDL, + }, + }) + _require.NoError(err) + _require.NotNil(cResp.FilePermissionKey) + _require.Equal(cResp.Date.IsZero(), false) + _require.NotNil(cResp.ETag) + _require.Equal(cResp.LastModified.IsZero(), false) + _require.NotNil(cResp.RequestID) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(*gResp.FilePermissionKey, *cResp.FilePermissionKey) + _require.EqualValues(gResp.Metadata, md) + + // Creating again will result in 409 and ResourceAlreadyExists. + _, err = dirClient.Create(context.Background(), &directory.CreateOptions{Metadata: md}) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ResourceAlreadyExists) + + dResp, err := dirClient.Delete(context.Background(), nil) + _require.NoError(err) + _require.Equal(dResp.Date.IsZero(), false) + _require.NotNil(dResp.RequestID) + _require.NotNil(dResp.Version) + + _, err = dirClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ResourceNotFound) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateNegativePermissions() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.GetDirectoryClient(dirName, shareClient) + subDirClient := dirClient.NewSubdirectoryClient("subdir" + dirName) + + cResp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(cResp.FilePermissionKey) + + // having both Permission and PermissionKey set returns error + _, err = subDirClient.Create(context.Background(), &directory.CreateOptions{ + FileSMBProperties: &file.SMBProperties{ + Attributes: &file.NTFSFileAttributes{None: true}, + }, + FilePermissions: &file.Permissions{ + Permission: &testcommon.SampleSDDL, + PermissionKey: cResp.FilePermissionKey, + }, + }) + _require.Error(err) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateNegativeAttributes() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirClient := testcommon.GetDirectoryClient(testcommon.GenerateDirectoryName(testName), shareClient) + + // None attribute must be used alone. + _, err = dirClient.Create(context.Background(), &directory.CreateOptions{ + FileSMBProperties: &file.SMBProperties{ + Attributes: &file.NTFSFileAttributes{None: true, ReadOnly: true}, + }, + }) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.InvalidHeaderValue) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateDeleteNegativeMultiLevelDir() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + parentDirName := "parent" + testcommon.GenerateDirectoryName(testName) + parentDirClient := shareClient.NewDirectoryClient(parentDirName) + + subDirName := "subdir" + testcommon.GenerateDirectoryName(testName) + subDirClient := parentDirClient.NewSubdirectoryClient(subDirName) + + // Directory create with subDirClient + _, err = subDirClient.Create(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ParentNotFound) + + _, err = parentDirClient.Create(context.Background(), nil) + _require.NoError(err) + + _, err = subDirClient.Create(context.Background(), nil) + _require.NoError(err) + + _, err = subDirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + + // Delete level by level + // Delete Non-empty directory should fail + _, err = parentDirClient.Delete(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.DirectoryNotEmpty) + + _, err = subDirClient.Delete(context.Background(), nil) + _require.NoError(err) + + _, err = parentDirClient.Delete(context.Background(), nil) + _require.NoError(err) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirCreateEndWithSlash() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + "/" + dirClient := testcommon.GetDirectoryClient(dirName, shareClient) + + cResp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.Equal(cResp.Date.IsZero(), false) + _require.NotNil(cResp.ETag) + _require.Equal(cResp.LastModified.IsZero(), false) + _require.NotNil(cResp.RequestID) + _require.NotNil(cResp.Version) + + _, err = dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirGetSetMetadataDefault() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.CreateNewDirectory(context.Background(), _require, dirName, shareClient) + defer testcommon.DeleteDirectory(context.Background(), _require, dirClient) + + sResp, err := dirClient.SetMetadata(context.Background(), nil) + _require.NoError(err) + _require.Equal(sResp.Date.IsZero(), false) + _require.NotNil(sResp.ETag) + _require.NotNil(sResp.RequestID) + _require.NotNil(sResp.Version) + _require.NotNil(sResp.IsServerEncrypted) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(gResp.Date.IsZero(), false) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.Version) + _require.NotNil(gResp.IsServerEncrypted) + _require.Len(gResp.Metadata, 0) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirGetSetMetadataNonDefault() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.CreateNewDirectory(context.Background(), _require, dirName, shareClient) + defer testcommon.DeleteDirectory(context.Background(), _require, dirClient) + + md := map[string]*string{ + "Foo": to.Ptr("FooValuE"), + "Bar": to.Ptr("bArvaLue"), + } + + sResp, err := dirClient.SetMetadata(context.Background(), &directory.SetMetadataOptions{ + Metadata: md, + }) + _require.NoError(err) + _require.Equal(sResp.Date.IsZero(), false) + _require.NotNil(sResp.ETag) + _require.NotNil(sResp.RequestID) + _require.NotNil(sResp.Version) + _require.NotNil(sResp.IsServerEncrypted) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(gResp.Date.IsZero(), false) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.Version) + _require.NotNil(gResp.IsServerEncrypted) + _require.EqualValues(gResp.Metadata, md) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirSetMetadataNegative() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.CreateNewDirectory(context.Background(), _require, dirName, shareClient) + defer testcommon.DeleteDirectory(context.Background(), _require, dirClient) + + md := map[string]*string{ + "!@#$%^&*()": to.Ptr("!@#$%^&*()"), + } + + _, err = dirClient.SetMetadata(context.Background(), &directory.SetMetadataOptions{ + Metadata: md, + }) + _require.Error(err) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirGetPropertiesNegative() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.GetDirectoryClient(dirName, shareClient) + + _, err = dirClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ResourceNotFound) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirGetPropertiesWithBaseDirectory() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirClient := shareClient.NewRootDirectoryClient() + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.Version) + _require.Equal(gResp.Date.IsZero(), false) + _require.Equal(gResp.FileCreationTime.IsZero(), false) + _require.Equal(gResp.FileLastWriteTime.IsZero(), false) + _require.Equal(gResp.FileChangeTime.IsZero(), false) + _require.NotNil(gResp.IsServerEncrypted) +} + +func (d *DirectoryUnrecordedTestsSuite) TestDirGetSetMetadataMergeAndReplace() { + _require := require.New(d.T()) + testName := d.T().Name() + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirClient := testcommon.CreateNewDirectory(context.Background(), _require, dirName, shareClient) + defer testcommon.DeleteDirectory(context.Background(), _require, dirClient) + + md := map[string]*string{ + "Color": to.Ptr("RED"), + } + + sResp, err := dirClient.SetMetadata(context.Background(), &directory.SetMetadataOptions{ + Metadata: md, + }) + _require.NoError(err) + _require.NotNil(sResp.RequestID) + _require.NotNil(sResp.IsServerEncrypted) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(gResp.Date.IsZero(), false) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.Version) + _require.NotNil(gResp.IsServerEncrypted) + _require.EqualValues(gResp.Metadata, md) + + md2 := map[string]*string{ + "Color": to.Ptr("WHITE"), + } + + sResp, err = dirClient.SetMetadata(context.Background(), &directory.SetMetadataOptions{ + Metadata: md2, + }) + _require.NoError(err) + _require.NotNil(sResp.RequestID) + _require.NotNil(sResp.IsServerEncrypted) + + gResp, err = dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(gResp.Date.IsZero(), false) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.Version) + _require.NotNil(gResp.IsServerEncrypted) + _require.EqualValues(gResp.Metadata, md2) +} + +func (d *DirectoryUnrecordedTestsSuite) TestSASDirectoryClientNoKey() { + _require := require.New(d.T()) + accountName, err := testcommon.GetRequiredEnv(testcommon.AccountNameEnvVar) + _require.NoError(err) + + testName := d.T().Name() + shareName := testcommon.GenerateShareName(testName) + dirName := testcommon.GenerateDirectoryName(testName) + dirClient, err := directory.NewClientWithNoCredential(fmt.Sprintf("https://%s.file.core.windows.net/%v/%v", accountName, shareName, dirName), nil) + _require.NoError(err) + + permissions := sas.FilePermissions{ + Read: true, + Write: true, + Delete: true, + Create: true, + } + expiry := time.Now().Add(time.Hour) + + _, err = dirClient.GetSASURL(permissions, expiry, nil) + _require.Equal(err, fileerror.MissingSharedKeyCredential) +} + +func (d *DirectoryUnrecordedTestsSuite) TestSASDirectoryClientSignNegative() { + _require := require.New(d.T()) + accountName, err := testcommon.GetRequiredEnv(testcommon.AccountNameEnvVar) + _require.NoError(err) + accountKey, err := testcommon.GetRequiredEnv(testcommon.AccountKeyEnvVar) + _require.NoError(err) + + cred, err := service.NewSharedKeyCredential(accountName, accountKey) + _require.NoError(err) + + testName := d.T().Name() + shareName := testcommon.GenerateShareName(testName) + dirName := testcommon.GenerateDirectoryName(testName) + dirClient, err := directory.NewClientWithSharedKeyCredential(fmt.Sprintf("https://%s.file.core.windows.net/%v%v", accountName, shareName, dirName), cred, nil) + _require.NoError(err) + + permissions := sas.FilePermissions{ + Read: true, + Write: true, + Delete: true, + Create: true, + } + expiry := time.Time{} + + _, err = dirClient.GetSASURL(permissions, expiry, nil) + _require.Equal(err.Error(), "service SAS is missing at least one of these: ExpiryTime or Permissions") +} + +// TODO: add tests for listing files and directories after file client is completed + +// TODO: add tests for ListHandles and ForceCloseHandles diff --git a/sdk/storage/azfile/directory/constants.go b/sdk/storage/azfile/directory/constants.go index 2574d573fc2c..2b16931bbc56 100644 --- a/sdk/storage/azfile/directory/constants.go +++ b/sdk/storage/azfile/directory/constants.go @@ -13,7 +13,7 @@ type ListFilesIncludeType = generated.ListFilesIncludeType const ( ListFilesIncludeTypeTimestamps ListFilesIncludeType = generated.ListFilesIncludeTypeTimestamps - ListFilesIncludeTypeEtag ListFilesIncludeType = generated.ListFilesIncludeTypeEtag + ListFilesIncludeTypeETag ListFilesIncludeType = generated.ListFilesIncludeTypeEtag ListFilesIncludeTypeAttributes ListFilesIncludeType = generated.ListFilesIncludeTypeAttributes ListFilesIncludeTypePermissionKey ListFilesIncludeType = generated.ListFilesIncludeTypePermissionKey ) diff --git a/sdk/storage/azfile/directory/models.go b/sdk/storage/azfile/directory/models.go index 033a4a8bd30b..82ccf027f487 100644 --- a/sdk/storage/azfile/directory/models.go +++ b/sdk/storage/azfile/directory/models.go @@ -7,9 +7,13 @@ package directory import ( + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/file" "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" + "reflect" + "time" ) // SharedKeyCredential contains an account's name and its primary or secondary key. @@ -28,6 +32,26 @@ type CreateOptions struct { Metadata map[string]*string } +func (o *CreateOptions) format() (fileAttributes string, fileCreationTime string, fileLastWriteTime string, createOptions *generated.DirectoryClientCreateOptions) { + if o == nil { + return shared.FileAttributesDirectory, shared.DefaultCurrentTimeString, shared.DefaultCurrentTimeString, &generated.DirectoryClientCreateOptions{ + FilePermission: to.Ptr(shared.DefaultFilePermissionString), + } + } + + fileAttributes, fileCreationTime, fileLastWriteTime = o.FileSMBProperties.Format(true, shared.FileAttributesDirectory, shared.DefaultCurrentTimeString) + + permission, permissionKey := o.FilePermissions.Format(shared.DefaultFilePermissionString) + + createOptions = &generated.DirectoryClientCreateOptions{ + FilePermission: permission, + FilePermissionKey: permissionKey, + Metadata: o.Metadata, + } + + return +} + // --------------------------------------------------------------------------------------------------------------------- // DeleteOptions contains the optional parameters for the Client.Delete method. @@ -35,6 +59,10 @@ type DeleteOptions struct { // placeholder for future options } +func (o *DeleteOptions) format() *generated.DirectoryClientDeleteOptions { + return nil +} + // --------------------------------------------------------------------------------------------------------------------- // GetPropertiesOptions contains the optional parameters for the Client.GetProperties method. @@ -43,6 +71,16 @@ type GetPropertiesOptions struct { ShareSnapshot *string } +func (o *GetPropertiesOptions) format() *generated.DirectoryClientGetPropertiesOptions { + if o == nil { + return nil + } + + return &generated.DirectoryClientGetPropertiesOptions{ + Sharesnapshot: o.ShareSnapshot, + } +} + // --------------------------------------------------------------------------------------------------------------------- // SetPropertiesOptions contains the optional parameters for the Client.SetProperties method. @@ -54,6 +92,24 @@ type SetPropertiesOptions struct { FilePermissions *file.Permissions } +func (o *SetPropertiesOptions) format() (fileAttributes string, fileCreationTime string, fileLastWriteTime string, setPropertiesOptions *generated.DirectoryClientSetPropertiesOptions) { + if o == nil { + return shared.DefaultPreserveString, shared.DefaultPreserveString, shared.DefaultPreserveString, &generated.DirectoryClientSetPropertiesOptions{ + FilePermission: to.Ptr(shared.DefaultPreserveString), + } + } + + fileAttributes, fileCreationTime, fileLastWriteTime = o.FileSMBProperties.Format(true, shared.DefaultPreserveString, shared.DefaultPreserveString) + + permission, permissionKey := o.FilePermissions.Format(shared.DefaultPreserveString) + + setPropertiesOptions = &generated.DirectoryClientSetPropertiesOptions{ + FilePermission: permission, + FilePermissionKey: permissionKey, + } + return +} + // --------------------------------------------------------------------------------------------------------------------- // SetMetadataOptions contains the optional parameters for the Client.SetMetadata method. @@ -62,12 +118,22 @@ type SetMetadataOptions struct { Metadata map[string]*string } +func (o *SetMetadataOptions) format() *generated.DirectoryClientSetMetadataOptions { + if o == nil { + return nil + } + + return &generated.DirectoryClientSetMetadataOptions{ + Metadata: o.Metadata, + } +} + // --------------------------------------------------------------------------------------------------------------------- // ListFilesAndDirectoriesOptions contains the optional parameters for the Client.NewListFilesAndDirectoriesPager method. type ListFilesAndDirectoriesOptions struct { // Include this parameter to specify one or more datasets to include in the response. - Include []ListFilesIncludeType + Include ListFilesInclude // Include extended information. IncludeExtendedInfo *bool // A string value that identifies the portion of the list to be returned with the next list operation. The operation returns @@ -84,6 +150,34 @@ type ListFilesAndDirectoriesOptions struct { ShareSnapshot *string } +// ListFilesInclude specifies one or more datasets to include in the response. +type ListFilesInclude struct { + Timestamps, ETag, Attributes, PermissionKey bool +} + +func (l ListFilesInclude) format() []generated.ListFilesIncludeType { + if reflect.ValueOf(l).IsZero() { + return nil + } + + include := []generated.ListFilesIncludeType{} + + if l.Timestamps { + include = append(include, ListFilesIncludeTypeTimestamps) + } + if l.ETag { + include = append(include, ListFilesIncludeTypeETag) + } + if l.Attributes { + include = append(include, ListFilesIncludeTypeAttributes) + } + if l.PermissionKey { + include = append(include, ListFilesIncludeTypePermissionKey) + } + + return include +} + // FilesAndDirectoriesListSegment - Abstract for entries that can be listed from directory. type FilesAndDirectoriesListSegment = generated.FilesAndDirectoriesListSegment @@ -95,3 +189,85 @@ type File = generated.File // FileProperty - File properties. type FileProperty = generated.FileProperty + +// --------------------------------------------------------------------------------------------------------------------- + +// GetSASURLOptions contains the optional parameters for the Client.GetSASURL method. +type GetSASURLOptions struct { + StartTime *time.Time +} + +func (o *GetSASURLOptions) format() time.Time { + if o == nil { + return time.Time{} + } + + var st time.Time + if o.StartTime != nil { + st = o.StartTime.UTC() + } else { + st = time.Time{} + } + return st +} + +// --------------------------------------------------------------------------------------------------------------------- + +// ListHandlesOptions contains the optional parameters for the Client.ListHandles method. +type ListHandlesOptions struct { + // A string value that identifies the portion of the list to be returned with the next list operation. The operation returns + // a marker value within the response body if the list returned was not complete. + // The marker value may then be used in a subsequent call to request the next set of list items. The marker value is opaque + // to the client. + Marker *string + // Specifies the maximum number of entries to return. If the request does not specify maxresults, or specifies a value greater + // than 5,000, the server will return up to 5,000 items. + MaxResults *int32 + // Specifies operation should apply to the directory specified in the URI, its files, its subdirectories and their files. + Recursive *bool + // The snapshot parameter is an opaque DateTime value that, when present, specifies the share snapshot to query. + ShareSnapshot *string +} + +func (o *ListHandlesOptions) format() *generated.DirectoryClientListHandlesOptions { + if o == nil { + return nil + } + + return &generated.DirectoryClientListHandlesOptions{ + Marker: o.Marker, + Maxresults: o.MaxResults, + Recursive: o.Recursive, + Sharesnapshot: o.ShareSnapshot, + } +} + +// Handle - A listed Azure Storage handle item. +type Handle = generated.Handle + +// --------------------------------------------------------------------------------------------------------------------- + +// ForceCloseHandlesOptions contains the optional parameters for the Client.ForceCloseHandles method. +type ForceCloseHandlesOptions struct { + // A string value that identifies the portion of the list to be returned with the next list operation. The operation returns + // a marker value within the response body if the list returned was not complete. + // The marker value may then be used in a subsequent call to request the next set of list items. The marker value is opaque + // to the client. + Marker *string + // Specifies operation should apply to the directory specified in the URI, its files, its subdirectories and their files. + Recursive *bool + // The snapshot parameter is an opaque DateTime value that, when present, specifies the share snapshot to query. + ShareSnapshot *string +} + +func (o *ForceCloseHandlesOptions) format() *generated.DirectoryClientForceCloseHandlesOptions { + if o == nil { + return nil + } + + return &generated.DirectoryClientForceCloseHandlesOptions{ + Marker: o.Marker, + Recursive: o.Recursive, + Sharesnapshot: o.ShareSnapshot, + } +} diff --git a/sdk/storage/azfile/directory/responses.go b/sdk/storage/azfile/directory/responses.go index f057fed8538f..28f2470b10ba 100644 --- a/sdk/storage/azfile/directory/responses.go +++ b/sdk/storage/azfile/directory/responses.go @@ -28,3 +28,12 @@ type ListFilesAndDirectoriesResponse = generated.DirectoryClientListFilesAndDire // ListFilesAndDirectoriesSegmentResponse - An enumeration of directories and files. type ListFilesAndDirectoriesSegmentResponse = generated.ListFilesAndDirectoriesSegmentResponse + +// ListHandlesResponse contains the response from method Client.ListHandles. +type ListHandlesResponse = generated.DirectoryClientListHandlesResponse + +// ListHandlesSegmentResponse - An enumeration of handles. +type ListHandlesSegmentResponse = generated.ListHandlesResponse + +// ForceCloseHandlesResponse contains the response from method Client.ForceCloseHandles. +type ForceCloseHandlesResponse = generated.DirectoryClientForceCloseHandlesResponse diff --git a/sdk/storage/azfile/file/constants.go b/sdk/storage/azfile/file/constants.go index ace31128a979..6714af1dc69c 100644 --- a/sdk/storage/azfile/file/constants.go +++ b/sdk/storage/azfile/file/constants.go @@ -8,40 +8,6 @@ package file import "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/generated" -// NTFSFileAttributes for Files and Directories. -// The subset of attributes is listed at: https://learn.microsoft.com/en-us/rest/api/storageservices/set-file-properties#file-system-attributes. -// Their respective values are listed at: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants. -type NTFSFileAttributes uint32 - -const ( - Readonly NTFSFileAttributes = 1 - Hidden NTFSFileAttributes = 2 - System NTFSFileAttributes = 4 - Directory NTFSFileAttributes = 16 - Archive NTFSFileAttributes = 32 - None NTFSFileAttributes = 128 - Temporary NTFSFileAttributes = 256 - Offline NTFSFileAttributes = 4096 - NotContentIndexed NTFSFileAttributes = 8192 - NoScrubData NTFSFileAttributes = 131072 -) - -// PossibleNTFSFileAttributesValues returns the possible values for the NTFSFileAttributes const type. -func PossibleNTFSFileAttributesValues() []NTFSFileAttributes { - return []NTFSFileAttributes{ - Readonly, - Hidden, - System, - Directory, - Archive, - None, - Temporary, - Offline, - NotContentIndexed, - NoScrubData, - } -} - // CopyStatusType defines the states of the copy operation. type CopyStatusType = generated.CopyStatusType diff --git a/sdk/storage/azfile/file/models.go b/sdk/storage/azfile/file/models.go index 5c95b0835a64..890959ca86f5 100644 --- a/sdk/storage/azfile/file/models.go +++ b/sdk/storage/azfile/file/models.go @@ -9,34 +9,20 @@ package file import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/generated" - "time" ) // SharedKeyCredential contains an account's name and its primary or secondary key. type SharedKeyCredential = exported.SharedKeyCredential // SMBProperties contains the optional parameters regarding the SMB/NTFS properties for a file. -type SMBProperties struct { - // NTFSFileAttributes for Files and Directories. Default value is ‘None’ for file and ‘Directory’ - // for directory. ‘None’ can also be specified as default. - Attributes *NTFSFileAttributes - // The Coordinated Universal Time (UTC) creation time for the file/directory. Default value is 'now'. - CreationTime *time.Time - // The Coordinated Universal Time (UTC) last write time for the file/directory. Default value is 'now'. - LastWriteTime *time.Time -} +type SMBProperties = exported.SMBProperties + +// NTFSFileAttributes for Files and Directories. +// The subset of attributes is listed at: https://learn.microsoft.com/en-us/rest/api/storageservices/set-file-properties#file-system-attributes. +type NTFSFileAttributes = exported.NTFSFileAttributes // Permissions contains the optional parameters for the permissions on the file. -type Permissions struct { - // If specified the permission (security descriptor) shall be set for the directory/file. This header can be used if Permission - // size is <= 8KB, else x-ms-file-permission-key header shall be used. Default - // value: Inherit. If SDDL is specified as input, it must have owner, group and dacl. Note: Only one of the x-ms-file-permission - // or x-ms-file-permission-key should be specified. - Permission *string - // Key of the permission to be set for the directory/file. - // Note: Only one of the x-ms-file-permission or x-ms-file-permission-key should be specified. - PermissionKey *string -} +type Permissions = exported.Permissions // HTTPHeaders contains optional parameters for the Client.Create method. type HTTPHeaders = generated.ShareFileHTTPHeaders diff --git a/sdk/storage/azfile/internal/exported/file_permissions.go b/sdk/storage/azfile/internal/exported/file_permissions.go new file mode 100644 index 000000000000..73fce6afb27c --- /dev/null +++ b/sdk/storage/azfile/internal/exported/file_permissions.go @@ -0,0 +1,32 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package exported + +// Permissions contains the optional parameters for the permissions on the file. +type Permissions struct { + // If specified the permission (security descriptor) shall be set for the directory/file. This header can be used if Permission + // size is <= 8KB, else x-ms-file-permission-key header shall be used. Default + // value: Inherit. If SDDL is specified as input, it must have owner, group and dacl. Note: Only one of the x-ms-file-permission + // or x-ms-file-permission-key should be specified. + Permission *string + // Key of the permission to be set for the directory/file. + // Note: Only one of the x-ms-file-permission or x-ms-file-permission-key should be specified. + PermissionKey *string +} + +// Format returns file permission string and permission key. +func (p *Permissions) Format(defaultFilePermissionStr string) (*string, *string) { + if p == nil { + return &defaultFilePermissionStr, nil + } + + if p.Permission == nil && p.PermissionKey == nil { + return &defaultFilePermissionStr, nil + } else { + return p.Permission, p.PermissionKey + } +} diff --git a/sdk/storage/azfile/internal/exported/smb_property.go b/sdk/storage/azfile/internal/exported/smb_property.go new file mode 100644 index 000000000000..894e9455b760 --- /dev/null +++ b/sdk/storage/azfile/internal/exported/smb_property.go @@ -0,0 +1,98 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package exported + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/internal/generated" + "strings" + "time" +) + +// SMBProperties contains the optional parameters regarding the SMB/NTFS properties for a file. +type SMBProperties struct { + // NTFSFileAttributes for Files and Directories. Default value is 'None' for file and + // 'Directory' for directory. ‘None’ can also be specified as default. + Attributes *NTFSFileAttributes + // The Coordinated Universal Time (UTC) creation time for the file/directory. Default value is 'now'. + CreationTime *time.Time + // The Coordinated Universal Time (UTC) last write time for the file/directory. Default value is 'now'. + LastWriteTime *time.Time +} + +// Format returns file attributes, creation time and last write time. +func (sp *SMBProperties) Format(isDir bool, defaultFileAttributes string, defaultCurrentTimeString string) (fileAttributes string, creationTime string, lastWriteTime string) { + if sp == nil { + return defaultFileAttributes, defaultCurrentTimeString, defaultCurrentTimeString + } + + fileAttributes = defaultFileAttributes + if sp.Attributes != nil { + fileAttributes = sp.Attributes.String() + if fileAttributes == "" { + fileAttributes = defaultFileAttributes + } else if isDir && strings.ToLower(fileAttributes) != "none" { + // Directories need to have this attribute included, if setting any attributes. + fileAttributes += "|Directory" + } + } + + creationTime = defaultCurrentTimeString + if sp.CreationTime != nil { + creationTime = sp.CreationTime.UTC().Format(generated.ISO8601) + } + + lastWriteTime = defaultCurrentTimeString + if sp.LastWriteTime != nil { + lastWriteTime = sp.LastWriteTime.UTC().Format(generated.ISO8601) + } + + return +} + +// NTFSFileAttributes for Files and Directories. +// The subset of attributes is listed at: https://learn.microsoft.com/en-us/rest/api/storageservices/set-file-properties#file-system-attributes. +type NTFSFileAttributes struct { + ReadOnly, Hidden, System, Directory, Archive, None, Temporary, Offline, NotContentIndexed, NoScrubData bool +} + +// String returns a string representation of NTFSFileAttributes. +func (f *NTFSFileAttributes) String() string { + fileAttributes := "" + if f.ReadOnly { + fileAttributes += "ReadOnly|" + } + if f.Hidden { + fileAttributes += "Hidden|" + } + if f.System { + fileAttributes += "System|" + } + if f.Directory { + fileAttributes += "Directory|" + } + if f.Archive { + fileAttributes += "Archive|" + } + if f.None { + fileAttributes += "None|" + } + if f.Temporary { + fileAttributes += "Temporary|" + } + if f.Offline { + fileAttributes += "Offline|" + } + if f.NotContentIndexed { + fileAttributes += "NotContentIndexed|" + } + if f.NoScrubData { + fileAttributes += "NoScrubData|" + } + + fileAttributes = strings.TrimSuffix(fileAttributes, "|") + return fileAttributes +} diff --git a/sdk/storage/azfile/internal/generated/autorest.md b/sdk/storage/azfile/internal/generated/autorest.md index 89e32751eb7f..ec6514bc1a44 100644 --- a/sdk/storage/azfile/internal/generated/autorest.md +++ b/sdk/storage/azfile/internal/generated/autorest.md @@ -244,3 +244,43 @@ directive: replace(/\(client \*ServiceClient\) listSharesSegmentCreateRequest\(/, `(client *ServiceClient) ListSharesSegmentCreateRequest(`). replace(/\(client \*ServiceClient\) listSharesSegmentHandleResponse\(/, `(client *ServiceClient) ListSharesSegmentHandleResponse(`); ``` + +### Use string type for FileCreationTime and FileLastWriteTime + +``` yaml +directive: +- from: swagger-document + where: $.parameters.FileCreationTime + transform: > + $.format = "str"; +- from: swagger-document + where: $.parameters.FileLastWriteTime + transform: > + $.format = "str"; +``` + +### Remove pager methods and export various generated methods in directory client + +``` yaml +directive: + - from: zz_directory_client.go + where: $ + transform: >- + return $. + replace(/func \(client \*DirectoryClient\) NewListFilesAndDirectoriesSegmentPager\(.+\/\/ listFilesAndDirectoriesSegmentCreateRequest creates the ListFilesAndDirectoriesSegment request/s, `//\n// listFilesAndDirectoriesSegmentCreateRequest creates the ListFilesAndDirectoriesSegment request`). + replace(/\(client \*DirectoryClient\) listFilesAndDirectoriesSegmentCreateRequest\(/, `(client *DirectoryClient) ListFilesAndDirectoriesSegmentCreateRequest(`). + replace(/\(client \*DirectoryClient\) listFilesAndDirectoriesSegmentHandleResponse\(/, `(client *DirectoryClient) ListFilesAndDirectoriesSegmentHandleResponse(`); +``` + +### Fix time format for parsing the response headers: x-ms-file-creation-time, x-ms-file-last-write-time, x-ms-file-change-time + +``` yaml +directive: + - from: zz_directory_client.go + where: $ + transform: >- + return $. + replace(/fileCreationTime,\s+err\s+\:=\s+time\.Parse\(time\.RFC1123,\s+val\)/g, `fileCreationTime, err := time.Parse(ISO8601, val)`). + replace(/fileLastWriteTime,\s+err\s+\:=\s+time\.Parse\(time\.RFC1123,\s+val\)/g, `fileLastWriteTime, err := time.Parse(ISO8601, val)`). + replace(/fileChangeTime,\s+err\s+\:=\s+time\.Parse\(time\.RFC1123,\s+val\)/g, `fileChangeTime, err := time.Parse(ISO8601, val)`); +``` diff --git a/sdk/storage/azfile/internal/generated/directory_client.go b/sdk/storage/azfile/internal/generated/directory_client.go index 1f07400bee9c..11a75a9f50c8 100644 --- a/sdk/storage/azfile/internal/generated/directory_client.go +++ b/sdk/storage/azfile/internal/generated/directory_client.go @@ -8,6 +8,11 @@ package generated import "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +const ( + // ISO8601 is used for formatting file creation, last write and change time. + ISO8601 = "2006-01-02T15:04:05.0000000Z07:00" +) + func (client *DirectoryClient) Endpoint() string { return client.endpoint } diff --git a/sdk/storage/azfile/internal/generated/zz_directory_client.go b/sdk/storage/azfile/internal/generated/zz_directory_client.go index b6256d2c5448..1b1eed71d03f 100644 --- a/sdk/storage/azfile/internal/generated/zz_directory_client.go +++ b/sdk/storage/azfile/internal/generated/zz_directory_client.go @@ -49,7 +49,7 @@ func NewDirectoryClient(endpoint string, pl runtime.Pipeline) *DirectoryClient { // - fileCreationTime - Creation time for the file/directory. Default value: Now. // - fileLastWriteTime - Last write time for the file/directory. Default value: Now. // - options - DirectoryClientCreateOptions contains the optional parameters for the DirectoryClient.Create method. -func (client *DirectoryClient) Create(ctx context.Context, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *DirectoryClientCreateOptions) (DirectoryClientCreateResponse, error) { +func (client *DirectoryClient) Create(ctx context.Context, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *DirectoryClientCreateOptions) (DirectoryClientCreateResponse, error) { req, err := client.createCreateRequest(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, options) if err != nil { return DirectoryClientCreateResponse{}, err @@ -65,7 +65,7 @@ func (client *DirectoryClient) Create(ctx context.Context, fileAttributes string } // createCreateRequest creates the Create request. -func (client *DirectoryClient) createCreateRequest(ctx context.Context, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *DirectoryClientCreateOptions) (*policy.Request, error) { +func (client *DirectoryClient) createCreateRequest(ctx context.Context, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *DirectoryClientCreateOptions) (*policy.Request, error) { req, err := runtime.NewRequest(ctx, http.MethodPut, client.endpoint) if err != nil { return nil, err @@ -91,8 +91,8 @@ func (client *DirectoryClient) createCreateRequest(ctx context.Context, fileAttr req.Raw().Header["x-ms-file-permission-key"] = []string{*options.FilePermissionKey} } req.Raw().Header["x-ms-file-attributes"] = []string{fileAttributes} - req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime.Format(time.RFC1123)} - req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime.Format(time.RFC1123)} + req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime} + req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime} req.Raw().Header["Accept"] = []string{"application/xml"} return req, nil } @@ -137,21 +137,21 @@ func (client *DirectoryClient) createHandleResponse(resp *http.Response) (Direct result.FileAttributes = &val } if val := resp.Header.Get("x-ms-file-creation-time"); val != "" { - fileCreationTime, err := time.Parse(time.RFC1123, val) + fileCreationTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientCreateResponse{}, err } result.FileCreationTime = &fileCreationTime } if val := resp.Header.Get("x-ms-file-last-write-time"); val != "" { - fileLastWriteTime, err := time.Parse(time.RFC1123, val) + fileLastWriteTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientCreateResponse{}, err } result.FileLastWriteTime = &fileLastWriteTime } if val := resp.Header.Get("x-ms-file-change-time"); val != "" { - fileChangeTime, err := time.Parse(time.RFC1123, val) + fileChangeTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientCreateResponse{}, err } @@ -397,21 +397,21 @@ func (client *DirectoryClient) getPropertiesHandleResponse(resp *http.Response) result.FileAttributes = &val } if val := resp.Header.Get("x-ms-file-creation-time"); val != "" { - fileCreationTime, err := time.Parse(time.RFC1123, val) + fileCreationTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientGetPropertiesResponse{}, err } result.FileCreationTime = &fileCreationTime } if val := resp.Header.Get("x-ms-file-last-write-time"); val != "" { - fileLastWriteTime, err := time.Parse(time.RFC1123, val) + fileLastWriteTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientGetPropertiesResponse{}, err } result.FileLastWriteTime = &fileLastWriteTime } if val := resp.Header.Get("x-ms-file-change-time"); val != "" { - fileChangeTime, err := time.Parse(time.RFC1123, val) + fileChangeTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientGetPropertiesResponse{}, err } @@ -435,36 +435,9 @@ func (client *DirectoryClient) getPropertiesHandleResponse(resp *http.Response) // Generated from API version 2020-10-02 // - options - DirectoryClientListFilesAndDirectoriesSegmentOptions contains the optional parameters for the DirectoryClient.NewListFilesAndDirectoriesSegmentPager // method. -func (client *DirectoryClient) NewListFilesAndDirectoriesSegmentPager(options *DirectoryClientListFilesAndDirectoriesSegmentOptions) *runtime.Pager[DirectoryClientListFilesAndDirectoriesSegmentResponse] { - return runtime.NewPager(runtime.PagingHandler[DirectoryClientListFilesAndDirectoriesSegmentResponse]{ - More: func(page DirectoryClientListFilesAndDirectoriesSegmentResponse) bool { - return page.NextMarker != nil && len(*page.NextMarker) > 0 - }, - Fetcher: func(ctx context.Context, page *DirectoryClientListFilesAndDirectoriesSegmentResponse) (DirectoryClientListFilesAndDirectoriesSegmentResponse, error) { - var req *policy.Request - var err error - if page == nil { - req, err = client.listFilesAndDirectoriesSegmentCreateRequest(ctx, options) - } else { - req, err = runtime.NewRequest(ctx, http.MethodGet, *page.NextMarker) - } - if err != nil { - return DirectoryClientListFilesAndDirectoriesSegmentResponse{}, err - } - resp, err := client.pl.Do(req) - if err != nil { - return DirectoryClientListFilesAndDirectoriesSegmentResponse{}, err - } - if !runtime.HasStatusCode(resp, http.StatusOK) { - return DirectoryClientListFilesAndDirectoriesSegmentResponse{}, runtime.NewResponseError(resp) - } - return client.listFilesAndDirectoriesSegmentHandleResponse(resp) - }, - }) -} - +// // listFilesAndDirectoriesSegmentCreateRequest creates the ListFilesAndDirectoriesSegment request. -func (client *DirectoryClient) listFilesAndDirectoriesSegmentCreateRequest(ctx context.Context, options *DirectoryClientListFilesAndDirectoriesSegmentOptions) (*policy.Request, error) { +func (client *DirectoryClient) ListFilesAndDirectoriesSegmentCreateRequest(ctx context.Context, options *DirectoryClientListFilesAndDirectoriesSegmentOptions) (*policy.Request, error) { req, err := runtime.NewRequest(ctx, http.MethodGet, client.endpoint) if err != nil { return nil, err @@ -500,7 +473,7 @@ func (client *DirectoryClient) listFilesAndDirectoriesSegmentCreateRequest(ctx c } // listFilesAndDirectoriesSegmentHandleResponse handles the ListFilesAndDirectoriesSegment response. -func (client *DirectoryClient) listFilesAndDirectoriesSegmentHandleResponse(resp *http.Response) (DirectoryClientListFilesAndDirectoriesSegmentResponse, error) { +func (client *DirectoryClient) ListFilesAndDirectoriesSegmentHandleResponse(resp *http.Response) (DirectoryClientListFilesAndDirectoriesSegmentResponse, error) { result := DirectoryClientListFilesAndDirectoriesSegmentResponse{} if val := resp.Header.Get("Content-Type"); val != "" { result.ContentType = &val @@ -681,7 +654,7 @@ func (client *DirectoryClient) setMetadataHandleResponse(resp *http.Response) (D // - fileCreationTime - Creation time for the file/directory. Default value: Now. // - fileLastWriteTime - Last write time for the file/directory. Default value: Now. // - options - DirectoryClientSetPropertiesOptions contains the optional parameters for the DirectoryClient.SetProperties method. -func (client *DirectoryClient) SetProperties(ctx context.Context, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *DirectoryClientSetPropertiesOptions) (DirectoryClientSetPropertiesResponse, error) { +func (client *DirectoryClient) SetProperties(ctx context.Context, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *DirectoryClientSetPropertiesOptions) (DirectoryClientSetPropertiesResponse, error) { req, err := client.setPropertiesCreateRequest(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, options) if err != nil { return DirectoryClientSetPropertiesResponse{}, err @@ -697,7 +670,7 @@ func (client *DirectoryClient) SetProperties(ctx context.Context, fileAttributes } // setPropertiesCreateRequest creates the SetProperties request. -func (client *DirectoryClient) setPropertiesCreateRequest(ctx context.Context, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *DirectoryClientSetPropertiesOptions) (*policy.Request, error) { +func (client *DirectoryClient) setPropertiesCreateRequest(ctx context.Context, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *DirectoryClientSetPropertiesOptions) (*policy.Request, error) { req, err := runtime.NewRequest(ctx, http.MethodPut, client.endpoint) if err != nil { return nil, err @@ -717,8 +690,8 @@ func (client *DirectoryClient) setPropertiesCreateRequest(ctx context.Context, f req.Raw().Header["x-ms-file-permission-key"] = []string{*options.FilePermissionKey} } req.Raw().Header["x-ms-file-attributes"] = []string{fileAttributes} - req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime.Format(time.RFC1123)} - req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime.Format(time.RFC1123)} + req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime} + req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime} req.Raw().Header["Accept"] = []string{"application/xml"} return req, nil } @@ -763,21 +736,21 @@ func (client *DirectoryClient) setPropertiesHandleResponse(resp *http.Response) result.FileAttributes = &val } if val := resp.Header.Get("x-ms-file-creation-time"); val != "" { - fileCreationTime, err := time.Parse(time.RFC1123, val) + fileCreationTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientSetPropertiesResponse{}, err } result.FileCreationTime = &fileCreationTime } if val := resp.Header.Get("x-ms-file-last-write-time"); val != "" { - fileLastWriteTime, err := time.Parse(time.RFC1123, val) + fileLastWriteTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientSetPropertiesResponse{}, err } result.FileLastWriteTime = &fileLastWriteTime } if val := resp.Header.Get("x-ms-file-change-time"); val != "" { - fileChangeTime, err := time.Parse(time.RFC1123, val) + fileChangeTime, err := time.Parse(ISO8601, val) if err != nil { return DirectoryClientSetPropertiesResponse{}, err } diff --git a/sdk/storage/azfile/internal/generated/zz_file_client.go b/sdk/storage/azfile/internal/generated/zz_file_client.go index 28c9a23feca1..398f0741fc8d 100644 --- a/sdk/storage/azfile/internal/generated/zz_file_client.go +++ b/sdk/storage/azfile/internal/generated/zz_file_client.go @@ -359,7 +359,7 @@ func (client *FileClient) changeLeaseHandleResponse(resp *http.Response) (FileCl // - options - FileClientCreateOptions contains the optional parameters for the FileClient.Create method. // - ShareFileHTTPHeaders - ShareFileHTTPHeaders contains a group of parameters for the FileClient.Create method. // - LeaseAccessConditions - LeaseAccessConditions contains a group of parameters for the ShareClient.GetProperties method. -func (client *FileClient) Create(ctx context.Context, fileContentLength int64, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *FileClientCreateOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (FileClientCreateResponse, error) { +func (client *FileClient) Create(ctx context.Context, fileContentLength int64, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *FileClientCreateOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (FileClientCreateResponse, error) { req, err := client.createCreateRequest(ctx, fileContentLength, fileAttributes, fileCreationTime, fileLastWriteTime, options, shareFileHTTPHeaders, leaseAccessConditions) if err != nil { return FileClientCreateResponse{}, err @@ -375,7 +375,7 @@ func (client *FileClient) Create(ctx context.Context, fileContentLength int64, f } // createCreateRequest creates the Create request. -func (client *FileClient) createCreateRequest(ctx context.Context, fileContentLength int64, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *FileClientCreateOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (*policy.Request, error) { +func (client *FileClient) createCreateRequest(ctx context.Context, fileContentLength int64, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *FileClientCreateOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (*policy.Request, error) { req, err := runtime.NewRequest(ctx, http.MethodPut, client.endpoint) if err != nil { return nil, err @@ -420,8 +420,8 @@ func (client *FileClient) createCreateRequest(ctx context.Context, fileContentLe req.Raw().Header["x-ms-file-permission-key"] = []string{*options.FilePermissionKey} } req.Raw().Header["x-ms-file-attributes"] = []string{fileAttributes} - req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime.Format(time.RFC1123)} - req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime.Format(time.RFC1123)} + req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime} + req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime} if leaseAccessConditions != nil && leaseAccessConditions.LeaseID != nil { req.Raw().Header["x-ms-lease-id"] = []string{*leaseAccessConditions.LeaseID} } @@ -1268,7 +1268,7 @@ func (client *FileClient) releaseLeaseHandleResponse(resp *http.Response) (FileC // - options - FileClientSetHTTPHeadersOptions contains the optional parameters for the FileClient.SetHTTPHeaders method. // - ShareFileHTTPHeaders - ShareFileHTTPHeaders contains a group of parameters for the FileClient.Create method. // - LeaseAccessConditions - LeaseAccessConditions contains a group of parameters for the ShareClient.GetProperties method. -func (client *FileClient) SetHTTPHeaders(ctx context.Context, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *FileClientSetHTTPHeadersOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (FileClientSetHTTPHeadersResponse, error) { +func (client *FileClient) SetHTTPHeaders(ctx context.Context, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *FileClientSetHTTPHeadersOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (FileClientSetHTTPHeadersResponse, error) { req, err := client.setHTTPHeadersCreateRequest(ctx, fileAttributes, fileCreationTime, fileLastWriteTime, options, shareFileHTTPHeaders, leaseAccessConditions) if err != nil { return FileClientSetHTTPHeadersResponse{}, err @@ -1284,7 +1284,7 @@ func (client *FileClient) SetHTTPHeaders(ctx context.Context, fileAttributes str } // setHTTPHeadersCreateRequest creates the SetHTTPHeaders request. -func (client *FileClient) setHTTPHeadersCreateRequest(ctx context.Context, fileAttributes string, fileCreationTime time.Time, fileLastWriteTime time.Time, options *FileClientSetHTTPHeadersOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (*policy.Request, error) { +func (client *FileClient) setHTTPHeadersCreateRequest(ctx context.Context, fileAttributes string, fileCreationTime string, fileLastWriteTime string, options *FileClientSetHTTPHeadersOptions, shareFileHTTPHeaders *ShareFileHTTPHeaders, leaseAccessConditions *LeaseAccessConditions) (*policy.Request, error) { req, err := runtime.NewRequest(ctx, http.MethodPut, client.endpoint) if err != nil { return nil, err @@ -1324,8 +1324,8 @@ func (client *FileClient) setHTTPHeadersCreateRequest(ctx context.Context, fileA req.Raw().Header["x-ms-file-permission-key"] = []string{*options.FilePermissionKey} } req.Raw().Header["x-ms-file-attributes"] = []string{fileAttributes} - req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime.Format(time.RFC1123)} - req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime.Format(time.RFC1123)} + req.Raw().Header["x-ms-file-creation-time"] = []string{fileCreationTime} + req.Raw().Header["x-ms-file-last-write-time"] = []string{fileLastWriteTime} if leaseAccessConditions != nil && leaseAccessConditions.LeaseID != nil { req.Raw().Header["x-ms-lease-id"] = []string{*leaseAccessConditions.LeaseID} } diff --git a/sdk/storage/azfile/internal/shared/shared.go b/sdk/storage/azfile/internal/shared/shared.go index b2e04301a09f..106e058ce55b 100644 --- a/sdk/storage/azfile/internal/shared/shared.go +++ b/sdk/storage/azfile/internal/shared/shared.go @@ -14,9 +14,10 @@ import ( ) const ( - TokenScope = "https://storage.azure.com/.default" - StorageAnalyticsVersion = "1.0" + TokenScope = "https://storage.azure.com/.default" +) +const ( HeaderAuthorization = "Authorization" HeaderXmsDate = "x-ms-date" HeaderContentLength = "Content-Length" @@ -31,6 +32,28 @@ const ( HeaderRange = "Range" ) +const StorageAnalyticsVersion = "1.0" + +const ( + // DefaultFilePermissionString is a constant for all intents and purposes. + // Inherit inherits permissions from the parent folder (default when creating files/folders) + DefaultFilePermissionString = "inherit" + + // DefaultCurrentTimeString sets creation/last write times to now + DefaultCurrentTimeString = "now" + + // DefaultPreserveString preserves old permissions on the file/folder (default when updating properties) + DefaultPreserveString = "preserve" + + // FileAttributesNone is defaults for file attributes when creating file. + // This attribute is valid only when used alone. + FileAttributesNone = "None" + + // FileAttributesDirectory is defaults for file attributes when creating directory. + // The attribute that identifies a directory + FileAttributesDirectory = "Directory" +) + func GetClientOptions[T any](o *T) *T { if o == nil { return new(T) diff --git a/sdk/storage/azfile/internal/testcommon/clients_auth.go b/sdk/storage/azfile/internal/testcommon/clients_auth.go index 36fb1484a0e4..1e1bad7bda4c 100644 --- a/sdk/storage/azfile/internal/testcommon/clients_auth.go +++ b/sdk/storage/azfile/internal/testcommon/clients_auth.go @@ -14,6 +14,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/directory" "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/service" "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/share" "github.com/stretchr/testify/require" @@ -150,3 +151,19 @@ func DeleteShare(ctx context.Context, _require *require.Assertions, shareClient _, err := shareClient.Delete(ctx, nil) _require.NoError(err) } + +func GetDirectoryClient(dirName string, s *share.Client) *directory.Client { + return s.NewDirectoryClient(dirName) +} + +func CreateNewDirectory(ctx context.Context, _require *require.Assertions, dirName string, shareClient *share.Client) *directory.Client { + dirClient := GetDirectoryClient(dirName, shareClient) + _, err := dirClient.Create(ctx, nil) + _require.NoError(err) + return dirClient +} + +func DeleteDirectory(ctx context.Context, _require *require.Assertions, dirClient *directory.Client) { + _, err := dirClient.Delete(ctx, nil) + _require.NoError(err) +} diff --git a/sdk/storage/azfile/internal/testcommon/common.go b/sdk/storage/azfile/internal/testcommon/common.go index f5177ac7ba98..f81f0b982cad 100644 --- a/sdk/storage/azfile/internal/testcommon/common.go +++ b/sdk/storage/azfile/internal/testcommon/common.go @@ -21,6 +21,7 @@ import ( const ( SharePrefix = "gos" DirectoryPrefix = "godir" + FilePrefix = "gotestfile" ) func GenerateShareName(testName string) string { @@ -35,6 +36,10 @@ func GenerateDirectoryName(testName string) string { return DirectoryPrefix + GenerateEntityName(testName) } +func GenerateFileName(testName string) string { + return FilePrefix + GenerateEntityName(testName) +} + func ValidateFileErrorCode(_require *require.Assertions, err error, code fileerror.Code) { _require.Error(err) var responseErr *azcore.ResponseError diff --git a/sdk/storage/azfile/share/client_test.go b/sdk/storage/azfile/share/client_test.go index 6cae061010a9..95f23a11d0a0 100644 --- a/sdk/storage/azfile/share/client_test.go +++ b/sdk/storage/azfile/share/client_test.go @@ -163,17 +163,25 @@ func (s *ShareUnrecordedTestsSuite) TestShareClientUsingSAS() { _require.Error(err) testcommon.ValidateFileErrorCode(_require, err, fileerror.AuthorizationFailure) - // TODO: create directories and files and uncomment this - //dirCtr, fileCtr := 0, 0 - //pager := shareSASClient.NewRootDirectoryClient().NewListFilesAndDirectoriesPager(nil) - //for pager.More() { - // resp, err := pager.NextPage(context.Background()) - // _require.NoError(err) - // dirCtr += len(resp.Segment.Directories) - // fileCtr += len(resp.Segment.Files) - //} - //_require.Equal(dirCtr, 0) - //_require.Equal(fileCtr, 0) + // TODO: create files using shareSASClient + dirName1 := testcommon.GenerateDirectoryName(testName) + "1" + _, err = shareSASClient.NewDirectoryClient(dirName1).Create(context.Background(), nil) + _require.NoError(err) + + dirName2 := testcommon.GenerateDirectoryName(testName) + "2" + _, err = shareSASClient.NewDirectoryClient(dirName2).Create(context.Background(), nil) + _require.NoError(err) + + dirCtr, fileCtr := 0, 0 + pager := shareSASClient.NewRootDirectoryClient().NewListFilesAndDirectoriesPager(nil) + for pager.More() { + resp, err := pager.NextPage(context.Background()) + _require.NoError(err) + dirCtr += len(resp.Segment.Directories) + fileCtr += len(resp.Segment.Files) + } + _require.Equal(dirCtr, 2) + _require.Equal(fileCtr, 0) } func (s *ShareUnrecordedTestsSuite) TestShareCreateDeleteNonDefault() {