{"id":668,"date":"2018-10-22T11:12:19","date_gmt":"2018-10-22T09:12:19","guid":{"rendered":"http:\/\/cwiok.pl\/?p=668"},"modified":"2018-10-22T11:21:59","modified_gmt":"2018-10-22T09:21:59","slug":"revisited-delete-from-ftp-using-azure-data-factory","status":"publish","type":"post","link":"https:\/\/cwiok.pl\/index.php\/en\/2018\/10\/22\/revisited-delete-from-ftp-using-azure-data-factory\/","title":{"rendered":"Revisited: Delete From FTP using Azure Data Factory"},"content":{"rendered":"<p>Due to the demand I have decided to revisit the solution that deletes files from FTP from within a Data Factory pipeline. I believe this approach is a much cleaner design. It is also easier to debug and control.<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-675\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory.png\" alt=\"\" width=\"1500\" height=\"653\" srcset=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory.png 1500w, https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory-300x131.png 300w, https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory-768x334.png 768w, https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory-1024x446.png 1024w\" sizes=\"auto, (max-width: 1500px) 100vw, 1500px\" \/><\/a><\/div>\n<p>First make sure, you have read <a href=\"http:\/\/cwiok.pl\/index.php\/en\/2018\/10\/15\/setting-up-azure-key-vault\/\">my article on setting up a Key Vault<\/a>. It is crucial knownledge, as you are going to store FTP credentials there. Set it up and create two secrets \u2013 one for login and one for password. If you wish, you may also store your IP address.<\/p>\n<p>Next, create a new Azure Function:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-12.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-12.png\" alt=\"image\" width=\"941\" height=\"653\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>Make sure to pick v2:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-13.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-13.png\" alt=\"image\" width=\"724\" height=\"415\" border=\"0\" \/><\/a><\/div>\n<p>Next paste the code into the solution:<\/p>\n<pre class=\"toolbar:2 wrap:true lang:c# decode:true \">using System;\r\nusing System.IO;\r\nusing System.Threading.Tasks;\r\nusing Microsoft.AspNetCore.Mvc;\r\nusing Microsoft.Azure.WebJobs;\r\nusing Microsoft.Azure.WebJobs.Extensions.Http;\r\nusing Microsoft.AspNetCore.Http;\r\nusing Microsoft.Extensions.Logging;\r\nusing Newtonsoft.Json;\r\nusing System.Collections.Generic;\r\nusing System.Net;\r\nusing Microsoft.Azure.Services.AppAuthentication;\r\nusing Microsoft.Azure.KeyVault;\r\n\r\nnamespace DeleteFromFTP_Function\r\n{\r\n     public static class DeleteFromFTP\r\n     {\r\n         [FunctionName(\"DeleteFromFTP\")]\r\n         public static async Task&lt;IActionResult&gt; Run(\r\n             [HttpTrigger(AuthorizationLevel.Function, \"get\", \"post\", Route = null)] HttpRequest req,\r\n             ILogger log)\r\n         {\r\n             log.LogInformation(\"C# HTTP trigger function processed a request.\");\r\n\r\n            var azureServiceTokenProvider = new AzureServiceTokenProvider();\r\n             var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));\r\n             string Login = kv.GetSecretAsync(Environment.GetEnvironmentVariable(\"FTPLogin\")).Result.Value;\r\n             string Password = kv.GetSecretAsync(Environment.GetEnvironmentVariable(\"FTPPassword\")).Result.Value;\r\n\r\n           \r\n            \r\n             string Path = req.Query[\"Path\"];\r\n             string IP = req.Query[\"IP\"];\r\n           \r\n             string requestBody = await new StreamReader(req.Body).ReadToEndAsync();\r\n             dynamic data = JsonConvert.DeserializeObject(requestBody);\r\n             Path = Path ?? data?.Path;\r\n             IP = IP ?? data?.IP;\r\n\r\n\r\n             DeleteFTPDirectory(Path + \"\/\", IP, Login, Password);\r\n             return (ActionResult)new OkObjectResult(data);\r\n\r\n           \r\n\r\n               \r\n         }\r\n         public static List&lt;string&gt; DirectoryListing(string Path, string ServerAdress, string Login, string Password)\r\n         {\r\n             FtpWebRequest request = (FtpWebRequest)WebRequest.Create(\"ftp:\/\/\" + ServerAdress + Path);\r\n             request.Credentials = new NetworkCredential(Login, Password);\r\n\r\n            request.Method = WebRequestMethods.Ftp.ListDirectory;\r\n\r\n            FtpWebResponse response = (FtpWebResponse)request.GetResponse();\r\n             Stream responseStream = response.GetResponseStream();\r\n             StreamReader reader = new StreamReader(responseStream);\r\n\r\n            List&lt;string&gt; result = new List&lt;string&gt;();\r\n\r\n            while (!reader.EndOfStream)\r\n             {\r\n                 result.Add(reader.ReadLine());\r\n             }\r\n\r\n            reader.Close();\r\n             response.Close();\r\n\r\n            return result;\r\n         }\r\n         public static void DeleteFTPFile(string Path, string ServerAdress, string Login, string Password)\r\n         {\r\n             FtpWebRequest clsRequest = (System.Net.FtpWebRequest)WebRequest.Create(\"ftp:\/\/\" + ServerAdress + Path);\r\n             clsRequest.Credentials = new System.Net.NetworkCredential(Login, Password);\r\n\r\n            clsRequest.Method = WebRequestMethods.Ftp.DeleteFile;\r\n\r\n            string result = string.Empty;\r\n             FtpWebResponse response = (FtpWebResponse)clsRequest.GetResponse();\r\n             long size = response.ContentLength;\r\n             Stream datastream = response.GetResponseStream();\r\n             StreamReader sr = new StreamReader(datastream);\r\n             result = sr.ReadToEnd();\r\n             sr.Close();\r\n             datastream.Close();\r\n             response.Close();\r\n         }\r\n         public static void DeleteFTPDirectory(string Path, string ServerAdress, string Login, string Password)\r\n         {\r\n             FtpWebRequest clsRequest = (System.Net.FtpWebRequest)WebRequest.Create(\"ftp:\/\/\" + ServerAdress + Path);\r\n             clsRequest.Credentials = new System.Net.NetworkCredential(Login, Password);\r\n\r\n            List&lt;string&gt; filesList = DirectoryListing(Path, ServerAdress, Login, Password);\r\n\r\n            foreach (string file in filesList)\r\n             {\r\n                 DeleteFTPFile(Path + file, ServerAdress, Login, Password);\r\n             }\r\n\r\n\r\n         }\r\n     }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>Notice, that Login and Password are taken from the Key Vault:<\/p>\n<pre class=\"toolbar:2 wrap:true lang:c# decode:true \">            string Login = kv.GetSecretAsync(Environment.GetEnvironmentVariable(\"FTPLogin\")).Result.Value;\r\n            string Password = kv.GetSecretAsync(Environment.GetEnvironmentVariable(\"FTPPassword\")).Result.Value;<\/pre>\n<p>&nbsp;<\/p>\n<p>Secret URLs are retrieved from Environmental variables that you can find in application settings:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-14.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-14.png\" alt=\"image\" width=\"566\" height=\"364\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>Under applications settings:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-15.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-15.png\" alt=\"image\" width=\"349\" height=\"274\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>Then, next step is to publish you function. You can do it from within Visual Studio by right-clicking on the project:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-16.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-16.png\" alt=\"image\" width=\"485\" height=\"181\" border=\"0\" \/><\/a><\/div>\n<p>In there pick the Azure Function that you want to publish to, or alternatively create a new one.<strong> Remember to register you app in Azure Active Directory and add it in access policies in your Key Vault.<\/strong><\/p>\n<p>After publishing, navigate to Azure Portal and test your function. It expects JSON in this format:<\/p>\n<pre class=\"toolbar:2 wrap:true lang:default decode:true \">{\r\n\r\n\"Path\":\"&lt;Path to the folder you want to clear&gt;\",\r\n\r\n\"IP\":\"&lt;IP of your FTP&gt;\"\r\n\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>If it does not work, you can debug it now.<\/p>\n<p>Next step is calling a POST request from Data Factory. To do it, you will use a web activity. However, beforehand you have to copy function URL. To get it navigate to main function page and click &lt;\/&gt; Get Function URL.<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-17.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-21.png\" alt=\"image\" width=\"644\" height=\"61\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>Copy the function from the URL filed:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-20.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-22.png\" alt=\"image\" width=\"646\" height=\"152\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>You are ready to go to your ADF pipeline and create a web activity:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-21.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-23.png\" alt=\"image\" width=\"204\" height=\"229\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>Next you should specify the URL (that you have just copied) and JSON in body with Path and IP \u2013 you can even try to set up some dynamic content here:<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image-22.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; display: inline; background-image: none;\" title=\"image\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/image_thumb-24.png\" alt=\"image\" width=\"559\" height=\"349\" border=\"0\" \/><\/a><\/div>\n<p>And that is it! Now you can trigger the function and see if it works.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Due to the demand I have decided to revisit the solution that deletes files from FTP from within a Data Factory pipeline. I believe this approach is a much cleaner design. It is also easier to debug and control.<\/p>\n<div align=\"center\"><a href=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-675\" src=\"http:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory.png\" alt=\"\" width=\"1500\" height=\"653\" srcset=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory.png 1500w, https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory-300x131.png 300w, https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory-768x334.png 768w, https:\/\/cwiok.pl\/wp-content\/uploads\/2018\/10\/this-is-azuredatafactory-1024x446.png 1024w\" sizes=\"auto, (max-width: 1500px) 100vw, 1500px\" \/><\/a><\/div>\n<div class=\"tech_read_more\"><a href=\"https:\/\/cwiok.pl\/index.php\/en\/2018\/10\/22\/revisited-delete-from-ftp-using-azure-data-factory\/\">Read More<\/a><\/div>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28],"tags":[],"class_list":["post-668","post","type-post","status-publish","format-standard","hentry","category-azure"],"_links":{"self":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts\/668","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/comments?post=668"}],"version-history":[{"count":0,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts\/668\/revisions"}],"wp:attachment":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/media?parent=668"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/categories?post=668"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/tags?post=668"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}