Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Azure File Services API module #23710

Conversation

Krajzys
Copy link
Contributor

@Krajzys Krajzys commented Jun 6, 2023

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.

Copy link
Contributor

@darjoo darjoo left a 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.

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';
Copy link
Contributor

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';
Copy link
Contributor

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';
Copy link
Contributor

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)
Copy link
Contributor

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

Add copyright section, some more documentation and some properties.
@Krajzys
Copy link
Contributor Author

Krajzys commented Jun 19, 2023

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:

  • AFS/ABS Web Request Helper:
    • Could be made common (no changes)
  • AFS/ABS Optional Parameters:
    • Could be made common, not sure if it would reduce complexity
    • Some headers are the same in both services
    • Some only occur in one of them
  • AFS/ABS Operation Payload:
    • Probably best to leave it separate
    • Could be made common if optional parameters codeunit and operations enum were common
    • ABS uses container name and blob name, AFS uses file share and path, Query and Table could use something else
  • AFS/ABS Operation Response:
    • Could be made common (no major changes)
    • Both are a universal wrapper for HttpResponseMessage
  • AFS/ABS HttpContent Helper:
    • Could be made common but would require a different implementation of procedures
    • Currently only two universal procedures
      • ContentSet
      • GetContentLength
  • AFS/ABS HttpHeader Helper:
    • Could be made common (some changes)
    • AFS uses a superset of ABS (some procedures to handle response headers)
  • AFS/ABS Format Helper:
    • Could be made common
    • AFS uses a subset of ABS (with an exception of one procedure)

Enums:

  • AFS/ABS Lease Action:
    • Could be made common
    • They are basically the same

Summary:

We could probably create a module that would have the following:

  • Universal Web Request Helper - Codeunit
  • Universal Operation Response - Codeunit
  • Universal HttpHeader Helper - Codeunit
  • Universal Format Helper - Codeunit
  • Universal Lease Action - Enum

I think it's not enough to justify it for now, what's your take on it?

@Krajzys Krajzys requested a review from darjoo June 20, 2023 11:55
@JesperSchulz JesperSchulz added the processing-PR The PR is currently being reviewed label Jul 7, 2023
@JesperSchulz
Copy link
Contributor

Time to get this rolling! We'll try to get this ready for release with 2023 Wave 2!

@darjoo
Copy link
Contributor

darjoo commented Jul 25, 2023

@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?
I'll at least add that to our internal E2E testing, just so that we can ensure this works :) Since doing unit tests with these modules don't work well without mocking capabilities. Fx ABS.

darjoo
darjoo previously approved these changes Jul 25, 2023
@Krajzys
Copy link
Contributor Author

Krajzys commented Jul 25, 2023

No problem :)
Yes, I wrote some basic tests in the meantime. I will push those changes here.

/// </summary>
enum 8956 "AFS File Permission Copy Mode"
{
Extensible = false;
Copy link
Contributor

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;
Copy link
Contributor

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"
{
Copy link
Contributor

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);
Copy link
Contributor

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?

@JesperSchulz
Copy link
Contributor

@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 😊

@pri-kise
Copy link
Contributor

@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.
https://appsource.microsoft.com/en-us/product/dynamics-365-business-central/PUBID.prisma_informatik_1762332%7CAID.pi-explorerforazurefileshare%7CPAPPID.d9dc02ea-b2c0-460c-b2be-219f1036249b?tab=Overview (See attached screenshots).

@JesperSchulz
Copy link
Contributor

OK, that's pretty cool! I should play with it 🥳

@Krajzys
Copy link
Contributor Author

Krajzys commented Aug 18, 2023

@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 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.

@JesperSchulz
Copy link
Contributor

That makes a lot of sense! Very cool. Let's do this!
23.0 is beginning to look a little tight, but 23.1 is definitely within reach!

@BW-PA
Copy link

BW-PA commented Aug 20, 2023

@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 😊

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.

@GhOnBc
Copy link

GhOnBc commented Aug 24, 2023

That makes a lot of sense! Very cool. Let's do this! 23.0 is beginning to look a little tight, but 23.1 is definitely within reach!

great news, really looking forward to see this available!
We also have a lot of scenarios where we would really benefit of being able to use azure file share instead of azure blob storage.

@GhOnBc
Copy link

GhOnBc commented Oct 2, 2023

That makes a lot of sense! Very cool. Let's do this! 23.0 is beginning to look a little tight, but 23.1 is definitely within reach!

Hi Jesper,
Is 23.1 still a possibility to have this PR included?

@JesperSchulz
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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."
Copy link
Contributor

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)

Copy link
Contributor Author

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"));
Copy link
Contributor

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));
Copy link
Contributor

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)));
Copy link
Contributor

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)
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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.

@Krajzys Krajzys requested a review from a team as a code owner October 11, 2023 12:00
@JesperSchulz JesperSchulz added the system application This PR is related to the system application and must be completed asap (migration in progress) label Nov 7, 2023
@Krajzys
Copy link
Contributor Author

Krajzys commented Nov 14, 2023

I'm coming back with two questions 😃

I've seen that the new repo BCApps went live.
Should I move the PR to there, or is it okay if it stays here?

Also could we get a status update on the PR processing?

@JesperSchulz
Copy link
Contributor

I'm coming back with two questions 😃

I've seen that the new repo BCApps went live. Should I move the PR to there, or is it okay if it stays here?

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 ⭐
When the PRs is in BCApps, it will get processed further until we can merge it. And then the job is finally done 😊
Sorry that this has dragged out a little, but we had to prioritize a few other efforts. But now those are done and we're ready to get this finalized!
Let me know what you prefer.

@Krajzys
Copy link
Contributor Author

Krajzys commented Nov 15, 2023

@JesperSchulz Thanks for the reply, I created an issue (microsoft/BCApps#340) and a PR (microsoft/BCApps#341) on the new repo .
If there is anything more I need to do please help me out.

@JesperSchulz
Copy link
Contributor

That was all which needed to be done. Thanks a lot for your help! Closing this PR.

JesperSchulz pushed a commit to microsoft/BCApps that referenced this pull request Dec 28, 2023
<!-- 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
processing-PR The PR is currently being reviewed system application This PR is related to the system application and must be completed asap (migration in progress)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants