Usuwanie plików z FTP przy pomocy Azure Data Factory. Podejście drugie.
Ze względu na popularność postu, postanowiłem zrobić drugie podejście do usuwania plików z FTP przy pomocy pipeline’u Data Factory. W mojej ocenie poniższe rozwiązane jest lepsze, łatwiejsze do debugowania i zapewnia większą kontrolę.
Najpierw, przeczytaj mój artykuł o Key Vault. Jest to istotna wiedza, ponieważ będziesz tam przechowywał poświadczenia do serwera FTP. Stwórz Key Vault i dwa sekrety – jeden dla loginu i jeden dla hasła. Możesz też przechować tam adres IP serwera.
Następnie, stwórz nową Azure Function:
Wybierz v2:
Następnie wklej poniższy kod do rozwiązania:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.Collections.Generic; using System.Net; using Microsoft.Azure.Services.AppAuthentication; using Microsoft.Azure.KeyVault; namespace DeleteFromFTP_Function { public static class DeleteFromFTP { [FunctionName("DeleteFromFTP")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); var azureServiceTokenProvider = new AzureServiceTokenProvider(); var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); string Login = kv.GetSecretAsync(Environment.GetEnvironmentVariable("FTPLogin")).Result.Value; string Password = kv.GetSecretAsync(Environment.GetEnvironmentVariable("FTPPassword")).Result.Value; string Path = req.Query["Path"]; string IP = req.Query["IP"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); Path = Path ?? data?.Path; IP = IP ?? data?.IP; DeleteFTPDirectory(Path + "/", IP, Login, Password); return (ActionResult)new OkObjectResult(data); } public static List<string> DirectoryListing(string Path, string ServerAdress, string Login, string Password) { FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://" + ServerAdress + Path); request.Credentials = new NetworkCredential(Login, Password); request.Method = WebRequestMethods.Ftp.ListDirectory; FtpWebResponse response = (FtpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); StreamReader reader = new StreamReader(responseStream); List<string> result = new List<string>(); while (!reader.EndOfStream) { result.Add(reader.ReadLine()); } reader.Close(); response.Close(); return result; } public static void DeleteFTPFile(string Path, string ServerAdress, string Login, string Password) { FtpWebRequest clsRequest = (System.Net.FtpWebRequest)WebRequest.Create("ftp://" + ServerAdress + Path); clsRequest.Credentials = new System.Net.NetworkCredential(Login, Password); clsRequest.Method = WebRequestMethods.Ftp.DeleteFile; string result = string.Empty; FtpWebResponse response = (FtpWebResponse)clsRequest.GetResponse(); long size = response.ContentLength; Stream datastream = response.GetResponseStream(); StreamReader sr = new StreamReader(datastream); result = sr.ReadToEnd(); sr.Close(); datastream.Close(); response.Close(); } public static void DeleteFTPDirectory(string Path, string ServerAdress, string Login, string Password) { FtpWebRequest clsRequest = (System.Net.FtpWebRequest)WebRequest.Create("ftp://" + ServerAdress + Path); clsRequest.Credentials = new System.Net.NetworkCredential(Login, Password); List<string> filesList = DirectoryListing(Path, ServerAdress, Login, Password); foreach (string file in filesList) { DeleteFTPFile(Path + file, ServerAdress, Login, Password); } } } } |
Zauważ, że login i hasło są pobierane z Key Vault:
1 2 |
string Login = kv.GetSecretAsync(Environment.GetEnvironmentVariable("FTPLogin")).Result.Value; string Password = kv.GetSecretAsync(Environment.GetEnvironmentVariable("FTPPassword")).Result.Value; |
Adresy URL sekretów są w zmiennych środowiskowych, które możesz znaleźć w ustawieniach aplikacji:
W “applications settings”:
Następny krok to publikacja funkcji. Możesz to zrobić z poziomu Visual Studio klikając prawym na projekt:
Przy publikacji wybierz Azure Function, do której chcesz wysłać swoja funkcję lub stwórz nową. Pamiętaj, żeby zarejestrować swoją aplikację w Azure Active Directory i dodać ją do access policies w Key Vault.
Po publikacji, przejdź do Azure Portal i przetestuj swoją funkcję. Oczekuje pliku JSON w takim formacie:
1 2 3 4 5 6 7 |
{ "Path":"<Path to the folder you want to clear>", "IP":"<IP of your FTP>" } |
Jeśli nie działa, możesz to naprawić tutaj.
Kolejnym krokiem jest wywołanie zapytania POST z poziomu Data Factory. Aby to zrobić, użyjesz web activity. Najpierw, skopiuj URL funkcji. Aby go zdobyć, przejdź do głównej menu funkcji i kliknij na </> Get Function URL:
Skopiuj adres URL:
Jesteś gotowy, żeby przejść do Data Factory. Stwórz web activity:
Następnie dodaj adres URL i Body w odpowiednim formacie.Możesz nawet dodać elementy dynamiczne:
I to tyle! Teraz możesz wywołać funkcję i sprawdzić jak działa.
Michał
Jedna rzecz, o której należy pamiętać!
Web activity ma wbudowany timeout, który wynosi 1 minutę!
I co gorsza, nie da się tego zmienić.
Jeśli logika w Azure Functions będzie trwała dłużej niż jedną minutę, activity zakońćzy się statusem błedu.
Na pocieszenie dodam, że Azure Functions nie powinien zakończyć logiki aplikacji, dobrnie ona do końca. Jednak ADF zgłaszając błąd pójdzie do następnych kroków (jeśli tak wynika z jego przepływów).
Niestety dokumentacja o tym wspomina, ale oczywiście łatwo to przeoczyć 🙁
https://docs.microsoft.com/en-us/azure/data-factory/control-flow-web-activity
“The activity will timeout at 1 minute with an error if it does not receive a response from the endpoint.”
p.s. “Dodaj komentarz” zamienia mi wszystkie litery na wielkie… to zamierzone? 😀