-
Notifications
You must be signed in to change notification settings - Fork 621
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
Add Azure File Services API module #23710
Add Azure File Services API module #23710
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice module that complements the Azure Blob Storage! Great work and looking forward to when we can merge this and ship!
Some comments here and there, but my biggest thought (which is also a comment somewhere) is how much can we combine between this and ABS? It definitely looked like some overlap which we may be able to extract without having to duplicate them and when we get Queue/Table services fx, they can hook into that.
Inregards to testing, I'm a little unsure on that point as well. At least when it comes to unit-testing.
We could come to terms and create tests which will only be run manually. Where if we make any changes, we'll run them against an actual storage account. Not the most convenient but it'll at least allow us to test everything simply by running the tests.
Modules/System/Azure File Services API/src/AFSClientImpl.Codeunit.al
Outdated
Show resolved
Hide resolved
CreateFileOperationNotSuccessfulErr: Label 'Could not create file %1 in %2.', Comment = '%1 = File Name; %2 = File Share Name'; | ||
PutFileOperationNotSuccessfulErr: Label 'Could not put file %1 ranges in %2.', Comment = '%1 = File Name; %2 = File Share Name'; | ||
CreateDirectoryOperationNotSuccessfulErr: Label 'Could not create directory %1 in %2.', Comment = '%1 = Directory Name; %2 = File Share Name'; | ||
GetFileOperationNotSuccessfulErr: Label 'Could not get File %1.', Comment = '%1 = File'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%1 = File Path
PutFileOperationNotSuccessfulErr: Label 'Could not put file %1 ranges in %2.', Comment = '%1 = File Name; %2 = File Share Name'; | ||
CreateDirectoryOperationNotSuccessfulErr: Label 'Could not create directory %1 in %2.', Comment = '%1 = Directory Name; %2 = File Share Name'; | ||
GetFileOperationNotSuccessfulErr: Label 'Could not get File %1.', Comment = '%1 = File'; | ||
CopyFileOperationNotSuccessfulErr: Label 'Could not copy File %1.', Comment = '%1 = File'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%1 = File Path
CopyFileOperationNotSuccessfulErr: Label 'Could not copy File %1.', Comment = '%1 = File'; | ||
DeleteFileOperationNotSuccessfulErr: Label 'Could not %3 File %1 in file share %2.', Comment = '%1 = File Name; %2 = File Share Name, %3 = Delete/Undelete'; | ||
DeleteDirectoryOperationNotSuccessfulErr: Label 'Could not delete directory %1 in file share %2.', Comment = '%1 = File Name; %2 = File Share Name'; | ||
AbortCopyFileOperationNotSuccessfulErr: Label 'Could not abort copy of File %1.', Comment = '%1 = File'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%1 = File Path
AFSOperationPayload.Initialize(StorageAccountName, FileShare, Path, Authorization, ApiVersion); | ||
end; | ||
|
||
internal procedure SetBaseUrl(BaseUrl: Text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All procedures in the impl are internal. internal keyword not required here, same for the rest in this codeunit
Modules/System/Azure File Services API/src/Helper/AFSWebRequestHelper.Codeunit.al
Show resolved
Hide resolved
Modules/System/Azure File Services API/src/Helper/AFSURIHelper.Codeunit.al
Show resolved
Hide resolved
Modules/System/Azure File Services API/src/Helper/AFSHttpHeaderHelper.Codeunit.al
Show resolved
Hide resolved
Modules/System/Azure File Services API/src/Helper/AFSHttpContentHelper.Codeunit.al
Show resolved
Hide resolved
Modules/System/Azure File Services API/src/Helper/AFSFormatHelper.Codeunit.al
Show resolved
Hide resolved
Add copyright section, some more documentation and some properties.
Regarding the point with similarities between Azure Blob Service API and Azure File Service API, I compiled a small list of object I think have some chance to be made common. Codeunits:
Enums:
Summary:We could probably create a module that would have the following:
I think it's not enough to justify it for now, what's your take on it? |
Time to get this rolling! We'll try to get this ready for release with 2023 Wave 2! |
@Krajzys Sorry for such a delay in response, summer and all. Agreed, and it would require more thinking of how we would maybe even combine HttpContent (Through interfaces?).. For a later discussion. I'll start processing the PR internally, but do you perhaps have a small app or something that you tested the module with? |
…into itintegro/add-azure-file-share-library
No problem :) |
/// </summary> | ||
enum 8956 "AFS File Permission Copy Mode" | ||
{ | ||
Extensible = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access Should be delcared explicit.
/// </summary> | ||
enum 8955 "AFS File Attribute" | ||
{ | ||
Extensible = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access should be delcared explicit, similiar to the other objects.
/// Holds procedures to format headers and parameters to be used in requests. | ||
/// </summary> | ||
codeunit 8956 "AFS Optional Parameters" | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access should be delcared explicit, similiar to the other objects.
HttpResponseMessage: HttpResponseMessage; | ||
begin | ||
if not HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then | ||
Error(OperationNotSuccessfulErr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we mention this error on the summary of the public methods of the Codeunit AFS Client?
@Krajzys, I hope you don't mind me asking, but in which cases is it better to use Azure File Storage than Azure Blob Storage? I've found a few articles, but they haven't really given me a solid answer... In which scenarios are you planning to use the File Storage? Forgive my ignorance, but storage isn't one of my strong suits and I'm trying to learn a little while processing the many storage related pull requests 😊 |
@JesperSchulz we use Azure File Share for our customers, since it can be mapped to drives in windows and can be used from there like any other network drive. |
OK, that's pretty cool! I should play with it 🥳 |
@JesperSchulz Just as @pri-kise mentioned 😃. We want to use azure file share drives to map them to windows drives. Some customers have file-based processes and it can be hard to convince them to switch to blob storage. That's when azure file share can shine, because you can make it look like a regular network drive or even map it to folder on a physical drive (the user then can interact with it as with a regular folder). In the end it's just another option to use in our toolkit. |
That makes a lot of sense! Very cool. Let's do this! |
Just adding my two cents. We use Azure Blob Storage now as a service to service integration between BC and a process that generates files for BC to import. To move the file into Blob Storage we needed to write a utility that monitors a local folder for new files and uploads them to Blob Storage. With Azure File Storage we can remove the need for this Utility app and map the Azure File Storage to a local folder. |
great news, really looking forward to see this available! |
Hi Jesper, |
Yes, I've started processing the PR internally and have received quite a few code review comments. I'll ask the developers to relay those comments here on GitHub (or I will do it). If we get those comments addressed before mid-October, the module could make the 23.1 release. Alternatively, we're looking at 23.2. |
{ | ||
DataClassification = SystemMetadata; | ||
Caption = 'Entry No.', Locked = true; | ||
Access = Internal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Any reason this is internal? Would be great with a comment on why. It should still be possible to access and modify this through RecordRef + FieldRef.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, not sure to be honest. As I mentioned this module was based on the module for blob storage, so it might be a leftover from there. I think the access modifier can be safely removed.
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
// ------------------------------------------------------------------------------------------------ | ||
|
||
codeunit 8951 "AFS Client Impl." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: "AFS File Client Impl." seems like a missing rename (aligning the interface and implementation codeunit)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I will fix this. Initially I wanted to split the interface codeunit into one for files and one for directories, but I came to conclusion that one codeunit will be more convenient.
EntryNo := GetNextEntryNo(AFSDirectoryContent); | ||
|
||
AFSDirectoryContent.Init(); | ||
AFSDirectoryContent."Parent Directory" := CopyStr(DirectoryPath, 1, MaxStrLen(AFSDirectoryContent."Parent Directory")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can put a limit on the input parameter we should always do that, I.E. "DirectoryPath: Text[MaxLength]". Otherwise we truncate text that may be unexpected. Someone extending this module and calling this may create a bug because they expect this could be any length.
AFSDirectoryContent.Insert(true); | ||
end; | ||
|
||
CurrentParent := CopyStr(AFSDirectoryContent."Full Name" + '/', 1, MaxStrLen(AFSDirectoryContent.Name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if adding the forwardslash causes this to overflow, wouldn't this fail? I.E. maybe we should throw an error in this case?
Clear(FieldNo); | ||
Field.Reset(); | ||
Field.SetRange(TableNo, TableNo); | ||
Field.SetRange("Field Caption", CopyStr(FieldCaption, 1, MaxStrLen(Field.FieldName))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can ignore this for now but in the future it would be good to limit the input FieldCaption variable even if it's obvious that you will truncate it
InherentPermissions = X; | ||
|
||
[NonDebuggable] | ||
procedure AppendToUri(var Uri: Text; ParameterIdentifier: Text; ParameterValue: Text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use UriBuilder. The current implementation does not encode the values, it may also be possible for non-admin users to mess up with the call if they have access to set just one of these parameters.
procedure ConvertToEnum(FieldName: Text; PropertyValue: Text): Variant | ||
begin | ||
if FieldName = 'Resource Type' then | ||
case PropertyValue of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LowerCase PropertyValue
end; | ||
end; | ||
|
||
procedure GetRfc1123DateTime(MyDateTime: DateTime): Text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can ignore this for now but in the future it would be good to move to another module along with FormatDateTime (something others may also use).
NewDateTime := 0DT; | ||
// PropertyValue is something like the following: 'Mon, 24 May 2021 12:25:27 GMT' | ||
// 'Evaluate' converts these correctly | ||
Evaluate(NewDateTime, PropertyValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you verify this with different locales? We have commonly seen issues when evaluating dates if you are using different date times. The date does look like it should work but just to be sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some tests with the two formats of dates that the azure file share api can send us back (RFC 1123 and ISO 8601) and changing up regions, time zones etc. in BC. Everything looked fine.
Clear(StorageBaseUrl); | ||
Clear(UriParameters); | ||
Clear(RequestHeaders); | ||
Clear(ContentHeaders); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like AFSOperation is not cleared.
I'm coming back with two questions 😃 I've seen that the new repo BCApps went live. Also could we get a status update on the PR processing? |
@Krajzys, I was just about to ask you, if you would like to port the PR to the BCApps repository! I could do that too, but I'd prefer that you do it, as you would then be listed as the owner of the initial PR for this module ⭐ |
@JesperSchulz Thanks for the reply, I created an issue (microsoft/BCApps#340) and a PR (microsoft/BCApps#341) on the new repo . |
That was all which needed to be done. Thanks a lot for your help! Closing this PR. |
<!-- Thank you for submitting a Pull Request. If you're new to contributing to BCApps please read our pull request guideline below * https://github.com/microsoft/BCApps/Contributing.md --> # Summary <!-- Provide a general summary of your changes --> This is a port of the pull request from the old repository, most of the convsersation and reviews are also there (microsoft/ALAppExtensions#23710). The goal of the PR is to add a module that facilitates working with Azure File Share service (which is a part of the Azure Cloud). The module is heavily based on the Azure Blob Storage Service API module, and most of the architecture is similair. There are also tests added for the module, but they should be ran manually, as they require passing credentials. Fixes #340 Work item: [AB#477758](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/477758)
Okay, this is a bit of a big PR, but I hope you can help me out.
We want to add a library to handle Azure File Share service in mostly the same way as Azure Blob Service is currently implemented.
We based this module heavily on the Azure Blob Services API module and it follows the same patterns.
I'm not sure how to go about the automatic testing process, because as far as I know azurite doesn't support Azure File Shares, so any tips on that would be helpful.