I have an Azure Function App which is triggered by the task Invoke Azure Function in DevOps Pipeline, because the Function App is taking so long I´m using the async mode in the function and the task is configured in callback mode. That all is working fine. The issue I´m having is that when an exception is occouring in my function app, I want to send a callback to my pipeline so it fails. So it doesn´t stay in waiting for response mode. Is there a way to fail a DevOps Pipeline with a callback?
I have been using this documentation from Microsoft.
Here is some code I tried:
var body = JsonConvert.SerializeObject(new
{
status = "Cancelling",
name = "Taskfailed",
taskId = taskInstanceId.ToString(),
jobId = jobId.ToString(),
result = "failed", // also tryed fail
}) ;
PostEvent(callbackUrl, body, authToken)
The function which is sending the callback
public static void PostEvent(String callbackUrl, String body, String authToken)
{
try
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var requestContent = new StringContent(body, Encoding.UTF8, "application/json");
var response = client.PostAsync(new Uri(callbackUrl), requestContent).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
log.LogInformation(response.StatusCode.ToString());
log.LogInformation(responseContent);
}
catch (Exception ex)
{
log.LogInformation("failed to cancel pipeline");
}
}
Related
I am working on a program that gets a list of workitems in the committed state from Azure DevOps for a specific area path and iteration path. My code is based on an example found at the following link: https://learn.microsoft.com/en-us/azure/devops/integrate/quickstarts/work-item-quickstart?view=azure-devops
The issue I am running into is when QueryByWiqlAsync() is called, the program terminates and there are no errors for why it terminated. Below is the code in question. I tried calling QueryByWiqlAsync() with and without the ConfigureAwait(false) and that did not seem to make a difference. Any suggestions on what to try or what to fix are appreciated.
static async void GetWorkItemsToTaskFromADO(string tfs_project, string accessToken)
{
var credentials = new VssBasicCredential(string.Empty, accessToken);
var wiql = new Wiql()
{
Query = #"Select [Id] From WorkItems WHERE [System.TeamProject] = 'SampleADOProject' AND [System.AreaPath] = 'Sample\ADO\AreaPath' AND [System.IterationPath] = 'Sample\ADO\IterationPath' AND [System.State] = 'Committed'"
};
using (var httpClient = new WorkItemTrackingHttpClient(new Uri(tfs_project), credentials))
{
try
{
var result = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
var ids = result.WorkItems.Select(item => item.Id).ToArray();
var fields = new[] { "System.Id", "System.Title", "System.State" };
var workItems = await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
// output results to test what came back...
foreach (var workItem in workItems)
{
Console.WriteLine(
"{0}\t{1}\t{2}",
workItem.Id,
workItem.Fields["System.Title"],
workItem.Fields["System.State"]
);
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
Console.Read();
}
}
}
I have a python azure function I'm trying to integrate with a C# backend. I'm trying to upload a file from an angular front end using a c# back end to post the data.
However, I'm getting a 401 error unauthorized. My function isnt anonymous level authentication and I'm attaching the keys to the headers, but is there something I'm missing that I need to include?
I've tried adding all the authentication to the headers and the form data headers but no luck.
public HttpResponseMessage UploadPartsTemplate()
{
MultipartFormDataContent form = new MultipartFormDataContent();
Dictionary<string, string> parameters = new Dictionary<string, string>();
string baseUrl = ConfigurationManager.AppSettings["PartsProjectAPIURL"];
try
{
var fileBytes = Request.Content.ReadAsStreamAsync().Result;
client.BaseAddress = new Uri(baseUrl);
HttpContent content = new StringContent("");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "file",
FileName = "template.xlsx"
};
content = new StreamContent(fileBytes);
form.Add(content, "template.xlsx");
form.Headers.Add("x-functions-key", ConfigurationManager.AppSettings["XFunctionsKey"]);
form.Headers.Add("Ocp-Apim-Subscription-Key", ConfigurationManager.AppSettings["OcmAPISubscriptionKey"]);
form.Headers.Add("Ocp-Apim-Trace", "true");
form.Headers.Add("command", "validate");
form.Headers.Add("code", ConfigurationManager.AppSettings["XFunctionsKey"]);
client.DefaultRequestHeaders.Add("x-functions-key", ConfigurationManager.AppSettings["XFunctionsKey"]);
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", ConfigurationManager.AppSettings["OcmAPISubscriptionKey"]);
client.DefaultRequestHeaders.Add("Ocp-Apim-Trace", "true");
client.DefaultRequestHeaders.Add("command", "validate");
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var response = client.PostAsync(baseUrl, form).Result;
return result;
}
catch(Exception ex)
{
throw;
}
}
And the POST Request in Postman works just fine. Any help would be appreciated!
Thank you!
GOAL:
I want to have an Azure Functions (HttpTrigger) that I can call through a Windows desktop app. I want the access to the function be controlled by Active Directory and only authorized users to be able to call it.
CURRENT STATUS:
I followed the guide here to create a desktop app, with AD authorization. I also created an Azure Function to which I added an "App Service Authentication" with "Log in with Azure Active Directory" and created a new app registration to handle this. In my desktop app I added a button that calls this function.
PROBLEM:
When I call the function directly through its link in a browser, everything works perfectly; if I am authorized, it calls the function, if I am not I am redirected to a log in screen and after a successful log in (for an authorized user only) I get the result of the function.
The problems come when I try to do this through my desktop app. When I press the function call button, I am redirected to the log in screen and as soon as I successfully log in with my credentials, I get the error:
AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: <app-id>
This happens when in my app registration I do not have an Authentication option for "Mobile and desktop applications", only for "Web".
If I add the "Mobile and desktop applications" option, then the original button (from the tutorial above) can log in and work properly (in the previous case, it is giving me the same error) but this time, when I try to call the function through the button I added, the program is crashing with the errors:
Inner Exception 1:
HttpRequestException: An error occurred while sending the request.
Inner Exception 2:
WebException: The underlying connection was closed: An unexpected error occurred on a send.
Inner Exception 3:
IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
Inner Exception 4:
SocketException: An existing connection was forcibly closed by the remote host
If I force the use of TLS 1.2 I get an 401 error: "You do not have permission to view this directory or page.". If I try to call a function that does not use AD authorization, then the whole process is successful. My code:
private async void CallFunctionButton_Click(object sender, RoutedEventArgs e)
{
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
authResult = await app.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(scopes)
.WithAccount(accounts.FirstOrDefault())
.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
if (authResult != null)
{
this.SignOutButton.Visibility = Visibility.Visible;
string token = authResult.AccessToken;
using (var client = new HttpClient())
{
// With an explicit selection of the security protocol the program does not crash.
// Instead it gives 401 Unauthorized error, when already signed in.
// Without the following line, the program crashes.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string requestUrl = $"the_URL_of_my_function";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = client.SendAsync(request).Result;
var responseString = response.Content.ReadAsStringAsync().Result;
ResultText.Text = responseString;
DisplayBasicTokenInfo(authResult);
}
}
}
QUESTION:
Can I call/use an Azure Function that needs authorization through a Windows desktop app and how?
Regarding the issue, it may relate to the TLS version. As fae as I knew, at the moment, Azure App Service will be created with TLS 1.2 by default. But, WPF application uses TLS 1.0 by default. So we cannot call the Azure function. Regarding how to fix it, please refer to the document
Update
Regarding how to call the Azure function projected by Azure AD, please refer to the following steps
Configure Azure AD for Azure Function
Create a client application in Azure AD
Configure API permissions and get the scope we nedd
code
string[] scopes = new string[]
{"https://testfun08.azurewebsites.net/user_impersonation" };// the scope you copy
private async void CallFunctionButton_Click(object sender, RoutedEventArgs e)
{
// get token
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
authResult = await app.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(scopes)
.WithAccount(accounts.FirstOrDefault())
.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
//call Azure function
if (authResult != null)
{
this.SignOutButton.Visibility = Visibility.Visible;
string token = authResult.AccessToken;
using (var client = new HttpClient())
{
// Without the following line, the program crashes.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string requestUrl = $"the_URL_of_my_function";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
HttpResponseMessage response = client.SendAsync(request).Result;
var responseString = response.Content.ReadAsStringAsync().Result;
ResultText.Text = responseString;
DisplayBasicTokenInfo(authResult);
}
}
}
I have been trying to intergrate the openxml package in Azure function. The code compiles fine but when i try to reach the function url it doesn't downloads the file but fails the call and there is no execution error in the code.
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log, [Inject]TrainingManager trainingManager)
{
//dynamic data = await req.Content.ReadAsAsync<object>();
//string trainingCourseId = data?.trainingCourseId;
log.Info("Function App Started");
HttpResponseMessage response = req.CreateResponse(HttpStatusCode.OK);
using (MemoryStream mem = new MemoryStream())
{
// Create Document
using (WordprocessingDocument wordDocument =
WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
// Add a main document part.
MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
// Create the document structure and add some text.
mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
Body docBody = new Body();
// Add your docx content here
Paragraph para = docBody.AppendChild(new Paragraph());
Run run = para.AppendChild(new Run());
run.AppendChild(new Text("Hi"));
mainPart.Document.Save();
}
mem.Seek(0, SeekOrigin.Begin);
response.Content = new StreamContent(mem);
}
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "AttendanceList.docx"
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/msword");
log.Info("Sending Response");
return response;
}
Let me know if anyone has faced this issue or have a solution to it.
Here is what i see in network tab]1
Thanks!
I am calling an async method InsertOperation from an async method ConfigureConnectionString. Am I using the client.OnMessage call correctly? I want to process the messages in a queue asynchronously and then store them to the queue storage.
private static async void ConfigureConnectionString()
{
var connectionString =
"myconnstring";
var queueName = "myqueue";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("test");
table.CreateIfNotExists();
Stopwatch sw = Stopwatch.StartNew();
await Task.Run(() => InsertOperation(connectionString, queueName, table));
sw.Stop();
Console.WriteLine("ElapsedTime " + sw.Elapsed.TotalMinutes + " minutes.");
}
private static async Task InsertOperation(string connectionString, string queueName, CloudTable table)
{
var client = QueueClient.CreateFromConnectionString(connectionString, queueName);
client.OnMessage(message =>
{
var bodyJson = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
var myMessage = JsonConvert.DeserializeObject<VerifyVariable>(bodyJson);
Console.WriteLine();
var VerifyVariableEntityObject = new VerifyVariableEntity()
{
ConsumerId = myMessage.ConsumerId,
Score = myMessage.Score,
PartitionKey = myMessage.ConsumerId,
RowKey = myMessage.Score
};
});
}
OnMessageAsync method provides async programming model, it enables us to process a message asynchronously.
client.OnMessageAsync(message =>
{
return Task.Factory.StartNew(() => ProcessMessage(message));
//you could perofrm table and queue storage in ProcessMessage method
}, options);
Without understanding the actual logic you want to achieve, it looks like you are not using OnMessage correctly.
OnMessage is a way to set up the queue client behavior for a long running client. It makes sense, for example, if you have a singleton instance in your application. In that case, you are specifing to the client how you want to handle any messages that are put in the queue.
In your example, however, you create the client, set up the OnMessage, and don't persist the client, so it effectively doesn't get anything accomplished.