Create Lambda proxy integration inside a non-root resource in API Gateway using AWS SDK with C# - c#

The bounty expires in 7 days. Answers to this question are eligible for a +100 reputation bounty.
Harun Ćerim is looking for an answer from a reputable source:
A detailed explanation of how Lambda integration can be done on a non-root resource inside an API Gateway where routes are currently mapped to an underlying Lambda function and its underlying routes
I am trying to create a proxy integration for Lambda invocation from API Gateway for a specific route (example.com/auth/{proxy+}, example.com/user/{proxy+}, etc..).
I am creating a script for automated infrastructure initialization and deployment (without using CloudFormation, Terraform, etc.) directly from .NET script that is planned to be available as an API.
Predefined infrastructure contains: Route53 config, created API Gateway with custom domain.
Dynamic infrastructure contains: S3 and Lambda together with API Gateway modifications and deployment.
Once the bucket for a new service is created and the built app is pushed to the bucket, new Lambda function is created, configured and published. The last thing that is left is to create a new resource (route) that will invoke underlying Lambda function and its underlying routes (e.g. example.com/auth/register).
The issue is that when I create an integration on a non-root resource, Lambda function cannot be found or the Uri is not specified as it should be (this is something I am trying to figure out).
Here is the simplified code that I wrote to accomplish this (I will exclude Lambda function and S3 bucket created and show only API gateway and Lambda resource policy updates as they are relevant here). Important to note is that this code produces the same results as if it would be done via AWS console. Also, this code produces a working solution if the route is not specified (e.g. example.com/register)
var functionArn = await Lambda.GetFunctionArn(accessKey, secretKey, region, lambdaFunction);
var pathResponse = await c.CreateResourceAsync(new CreateResourceRequest
{
ParentId = rootId,
PathPart = path,
RestApiId = apiId
});
await c.PutMethodAsync(new PutMethodRequest
{
AuthorizationType = "NONE",
HttpMethod = "ANY",
ResourceId = pathResponse.Id,
RestApiId = apiId
});
var proxyResponse = await c.CreateResourceAsync(new CreateResourceRequest
{
ParentId = pathResponse.Id,
PathPart = "{proxy+}",
RestApiId = apiId
});
await c.PutMethodAsync(new PutMethodRequest
{
AuthorizationType = "NONE",
HttpMethod = "ANY",
ResourceId = proxyResponse.Id,
RestApiId = apiId
});
await Lambda.AddPermissions(account, accessKey, secretKey, region, lambdaFunction, apiId, path);
await c.PutIntegrationAsync(new PutIntegrationRequest
{
HttpMethod = "ANY",
IntegrationHttpMethod = "POST",
ResourceId = pathResponse.Id,
RestApiId = apiId,
PassthroughBehavior = "WHEN_NO_MATCH",
Type = IntegrationType.AWS_PROXY,
Uri = $"arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{functionArn}/invocations"
});
await c.PutIntegrationAsync(new PutIntegrationRequest
{
HttpMethod = "ANY",
IntegrationHttpMethod = "POST",
ResourceId = proxyResponse.Id,
RestApiId = apiId,
PassthroughBehavior = "WHEN_NO_MATCH",
Type = IntegrationType.AWS_PROXY,
Uri = $"arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{functionArn}/invocations"
});
var deployment = await c.CreateDeploymentAsync(new CreateDeploymentRequest
{
Description = $"API deployment to {environment}",
RestApiId = apiId,
StageName = environment
});
return deployment.Id;
where Lambda.AddPermissions is as follows:
var basePermission = await c.AddPermissionAsync(new AddPermissionRequest
{
Action = "lambda:InvokeFunction",
FunctionName = name,
Principal = "apigateway.amazonaws.com",
SourceArn = $"arn:aws:execute-api:{region}:{account}:{apiId}/*/*/{path}/*",
StatementId = Guid.NewGuid().ToString()
});
var proxyPermission = await c.AddPermissionAsync(new AddPermissionRequest
{
Action = "lambda:InvokeFunction",
FunctionName = name,
Principal = "apigateway.amazonaws.com",
SourceArn = $"arn:aws:execute-api:{region}:{account}:{apiId}/*/*/{path}",
StatementId = Guid.NewGuid().ToString()
});
return new List<string>
{
basePermission.Statement,
proxyPermission.Statement
};
Is there an issue with SourceArn specifications? I first created them through the AWS console (they are automatically created when the integration is created for Lambda) and they are the same.
Again, this all works when there is no path (non-root resource).

Related

Not always a two way Related link in Azure DevOps using c#

I am creating a related link with the referenceName System.LinkTypes.Related in Azure DevOps programmatically using C# and Azure DevOps' SDK as below:
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add(
new JsonPatchOperation
{
From = null,
Operation = Operation.Add,
Path = "/relations/-",
Value = new {
rel = "System.LinkTypes.Related",
url = $"https://dev.azure.com/{organization}/{projectName}/_workitems/edit/{relatedId}",
attributes = new
{
comment = $"Created programmatically on {DateTime.Now}."
}
}
}
);
await azureClient.UpdateWorkItemAsync(patchDoc, id, false, true, true, WorkItemExpand.All, cancellationToken: token);
The code above always created a two way link between id and relatedId.
But sometimes the link is one way!
How can i be sure to always create a link in both directions?

Unit Test in Azure Function C#

I want to unit test my azure function API by sending mock request and response data. But my test is getting failed even if i pass same Json data on both request and response.
TestCode
[TestMethod]
public async Task ClinicReadTestMethod()
{
//Arrange
//var clinicRequest = new
//{
// Id = "1",
// OpenIdProvider = "Google",
// Subject = "Test",
// Name = "Test",
// Address = "Test",
// Email = "Test",
// Phone = "Test",
// Notes = "Test"
//};
var query = new Dictionary<string, StringValues>();
query.Add("openIdProvider", "Google");
query.Add("subject", "Test");
//var body = JsonSerializer.Serialize(clinicRequest);
var logger = Mock.Of<ILogger>();
var client = Mock.Of<CosmosClient>();
ContentResultFactory contentResultFactory = new ContentResultFactory();
//Act
var testFunction = new ClinicReadFunction(contentResultFactory);
var result = await testFunction.Run(TestFactory.HttpRequestSetup(query), client, logger); //fixme
var resultObject = JsonSerializer.Serialize(result as ContentResult);
//Assert
var clinicResponse = new
{
Id = "1",
openIdProvider = "Google",
subject = "Test",
Name = "Test",
Address = "Test",
Email = "Test",
Phone = "Test",
Notes = "Test"
};
var resultBody = JsonSerializer.Serialize(clinicResponse);
//var res = contentResultFactory.CreateContentResult(HttpStatusCode.OK);
Assert.AreEqual(resultBody, resultObject);
}
}
This is how my azure function looks like. It is taking two parameters and returning the response. I have tried to mock the data for unit test still no success. If anyone have idea how to unit test this azure function please let me know.
//AzureFunction
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "")] HttpRequest req,
[CosmosDB(
databaseName: "",
containerName: "",
Connection = ""
)] CosmosClient client,
ILogger log)
{
string subject = req.Query["sub"];
if (!Enum.TryParse(req.Query["idp"], out OpenIdProvider openIdProvider) || string.IsNullOrEmpty(subject))
{
var message = "";
log.LogWarning();
return _contentResultFactory.CreateContentResult(message, HttpStatusCode.BadRequest);
}
var query = client.GetContainer("", "").GetItemLinqQueryable<Clinic>()
.Where(x => x.OpenIdProvider == openIdProvider && x.Subject == subject);
Clinic clinic;
using (var iterator = query.ToFeedIterator())
clinic = (await iterator.ReadNextAsync()).FirstOrDefault();
if (clinic == null)
{
log.LogWarning();
return _contentResultFactory.CreateContentResult();
}
var response = new ClinicReadResponse(clinic);
return _contentResultFactory.CreateContentResult(response, HttpStatusCode.OK);
}
//TestFactory
public static HttpRequest HttpRequestSetup(Dictionary<string, StringValues> query)
{
var context = new DefaultHttpContext();
var request = context.Request;
request.Query = new QueryCollection(query);
request.Method = "GET";
return request;
}
In both your Clinic objects, your are generating a new GUID for the ID by calling System.Guid.NewGuid. Assuming the JSON generated from each object is the same shape (they will need to be if you want them to match), the values of each ID property will be different. Since the IDs are different, your JSON strings are not equal, therefore causing the failure.
Here is a post that will show you how to manually create a Guid. You can use this to ensure your IDs are of the same value when testing.
Assigning a GUID in C#
I don't know what your Azure Function code looks like, but your test's setup to make an HTTP request tells me you're calling the method tied to the Http Trigger. Consider the scope of what your method is doing; if it is large (or is calling other methods), this will increase the chances of your test breaking as you change the Azure Function over time. To help future-proof your test make sure the method it's calling has a single responsibility. This will make debugging your code easier to do if a change does make your test fail, and will lessen the likelihood of needing to edit your test to accommodate for code changes.

How can we add existing links in Azure DevOps programmatically

I want to link the existing work items which are already created in a project under Azure DevOps by writing a code or program in C#, so is there any kind of API or SDK which can be used to link the work items programmatically?
The Workitems can be of any type i.e.
Bug
User Story
Issue
Task etc.
The linking between the Workitems can also be of any type i.e. Relational, Parent-Child, etc.
Recently I referred to this link for my problem.
The link contains issue very much related and similar to mine, however it is not working as expected when I tried it.
Given:
//int relatedId = ...
//int id = ...
//CancellationToken token = ...
//string organization = ...
//string projectName = ...
//WorkItemTrackingHttpClient azureClient = ...
Create a JsonPatchDocument as below:
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add(
new JsonPatchOperation
{
From = null,
Operation = Operation.Add,
Path = "/relations/-",
Value = new {
rel = "System.LinkTypes.Related",
url = $"https://dev.azure.com/{organization}/{projectName}/_workitems/edit/{relatedId}",
attributes = new
{
comment = $"Created programmatically on {DateTime.Now}."
}
}
}
);
and call this async method of Azure DevOps SDK:
await azureClient.UpdateWorkItemAsync(patchDoc, id, false, true, true, WorkItemExpand.All, cancellationToken: token);
In the case above we created a related link using System.LinkTypes.Related referenceName.
For a full link types reference guide in Azure DevOps refer to this so's question or this microsoft's doc.

Need to create a folder(and a file inside it) using C# inside Azure DevOps repository - be it Git or TFVC

From Azure DevOps portal, I can manually add file/ folder into repository irrespective of the fact that source code is cloned or not - Image for illustration.
However, I want to programmatically create a folder and a file inside that folder within a Repository from c# code in my ASP .NET core application.
Is there a Azure DevOps service REST API or any other way to do that? I'll use BASIC authentication through PAT token only.
Note : I'm restricted to clone the source code at local repository.
Early reply is really appreciated.
I tried HttpClient, GitHttpClient and LibGit2Sharp but failed.
Follow below steps in your C# code
call GetRef REST https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/refs{3}
this should return the object of your repository branch which you can use to push your changes
Next, call Push REST API to create folder or file into your repository
https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/pushes{3}
var changes = new List<ChangeToAdd>();
//Add Files
//pnp_structure.yml
var jsonContent = File.ReadAllText(#"./static-files/somejsonfile.json");
ChangeToAdd changeJson = new ChangeToAdd()
{
changeType = "add",
item = new ItemBase() { path = string.Concat(path, "/[your-folder-name]/somejsonfile.json") },
newContent = new Newcontent()
{
contentType = "rawtext",
content = jsonContent
}
};
changes.Add(changeJson);
CommitToAdd commit = new CommitToAdd();
commit.comment = "commit from code";
commit.changes = changes.ToArray();
var content = new List<CommitToAdd>() { commit };
var request = new
{
refUpdates = refs,
commits = content
};
var personalaccesstoken = _configuration["azure-devOps-configuration-token"];
var authorization = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalaccesstoken)));
_logger.LogInformation($"[HTTP REQUEST] make a http call with uri: {uri} ");
//here I making http client call
// https://dev.azure.com/{orgnizationName}/{projectName}/_apis/git/repositories/{repositoryId}/pushes{?api-version}
var result = _httpClient.SendHttpWebRequest(uri, method, data, authorization);

ElasticSearch.NET passing AWS Signature v4 in POST requests?

I am very new to ElasticSearch, and have set up an AWS Lambda function in c# to take the content of S3 object(s) (which contain JSON data) with the hopes of posting them to ES to be searchable.
I'm using the Elasticsearch.Net nuget library.
In the documentation here - https://github.com/elastic/elasticsearch-net there is samples of configuring the node URI etc, but my understanding is that any requests to ES need to be signed with AWS Signature V4 (based on Access/Secret key). I have created an IAM user for this purpose, but nowhere in the documentation can I find how to sign the POST requests. The samples show post methods, but no place to include the signature?
E.g.
var person = new Person
{
FirstName = "Martijn",
LastName = "Laarman"
};
var indexResponse = lowlevelClient.Index<BytesResponse>("people", "person", "1", PostData.Serializable(person));
byte[] responseBytes = indexResponse.Body;
var asyncIndexResponse = await lowlevelClient.IndexAsync<StringResponse>("people", "person", "1", PostData.Serializable(person));
string responseString = asyncIndexResponse.Body;
Even when instantiating the connection, nowhere is there a place to add your credentials?
var settings = new ConnectionConfiguration(new Uri("http://example.com:9200"))
.RequestTimeout(TimeSpan.FromMinutes(2));
var lowlevelClient = new ElasticLowLevelClient(settings);
I have checked ConnectionConfiguration object but there's no methods or properties that seem related. What have I missed?
Using the elasticsearch-net-aws package you should be able to set up the low level client like so:
var httpConnection = new AwsHttpConnection("us-east-1"); // or whatever region you're using
var pool = new SingleNodeConnectionPool(new Uri(TestConfig.Endpoint));
var config = new ConnectionConfiguration(pool, httpConnection);
config.DisableDirectStreaming();
var client = new ElasticLowLevelClient(config);
// ...
// if using an access key
var httpConnection = new AwsHttpConnection("us-east-1", new StaticCredentialsProvider(new AwsCredentials
{
AccessKey = "My AWS access key",
SecretKey = "My AWS secret key",
}));
// if using app.config, environment variables, or roles
var httpConnection = new AwsHttpConnection("us-east-1");
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var config = new ConnectionSettings(pool, httpConnection);
var client = new ElasticClient(config);

Categories

Resources