I am trying to deploy an AWS CDK pipeline using C# that contains a ChangeSet approval rule. I am facing the following error:
Missing required properties for aws-cdk-lib.pipelines.StackSteps: stack
Here is my new Stage:
var x = new JournalAppStage(this, "test", new StageProps {
Env = new Environment {
Account = System.Environment.GetEnvironmentVariable("CDK_DEFAULT_ACCOUNT"),
Region = System.Environment.GetEnvironmentVariable("CDK_DEFAULT_REGION")
}
});
Here is the Stage definition:
using System.Collections.Generic;
using Amazon.CDK;
using Constructs;
namespace JournalCdk
{
public class JournalAppStage : Stage
{
public Stack journalStack;
public JournalAppStage(Construct scope, string id, StageProps props = null) : base(scope, id, props)
{
Stack journalStack = new JournalCdkStack(this, "JournalStack", new JournalCdkStackProps {
BucketName = System.Environment.GetEnvironmentVariable("BUCKET_NAME"),
Environment = System.Environment.GetEnvironmentVariable("ENVIRONMENT"),
});
}
}
}
And here is where I am trying to add the ChangeSet:
var uatPipelineStage = pipeline.AddStage(x, new AddStageOpts {
StackSteps = new [] { new StackSteps {
Stack = x.journalStack,
ChangeSet = new [] {
new ManualApprovalStep("ChangeSetApproval"),
}
}}
});
I have been knocking up against this for a while and cannot work out the correct way to add the StackSteps.
Related
I'm trying to use Azure.Maps.Search to give me a polygon for a result. For example, if I search for "Birmingham" I would like a result for that municipality with a collection of geopoints defining the boundary.
Is this possible?
var credential = new AzureKeyCredential("............");
var client = new MapsSearchClient(credential);
Response<SearchAddressResult> searchResult = await client.SearchAddressAsync(
query: "Birmingham",
options: new SearchAddressOptions
{
ExtendedPostalCodesFor=new SearchIndex[] { SearchIndex.PointAddresses },
CountryFilter = new string[] { "GB" },
Top = 1,
EntityType = GeographicEntity.Municipality
});
Yes, this is possible. The search address API will contain a DataSources.Geometries.ID value in the results that is the ID of the unique boundary for that result. You can take this ID and pass it into the GetPolygonsAsync API in the Azure.Maps.Search Nuget package.
using Azure;
using Azure.Maps.Search;
using Azure.Maps.Search.Models;
namespace AzureMapsTest
{
internal class Program
{
private const string MapsApiKey = "...........";
static async Task Main(string[] args)
{
var credential = new AzureKeyCredential(MapsApiKey);
var client = new MapsSearchClient(credential);
SearchAddressOptions singleSearchResultOptions = new SearchAddressOptions { Top = 1 };
Response<SearchAddressResult> searchResult =
await client.SearchAddressAsync(
"Ealing, London, England",
singleSearchResultOptions);
Response<PolygonResult> polygons =
await client.GetPolygonsAsync(new string[] { searchResult.Value.Results[0].DataSources.Geometry.Id });
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(polygons.Value.Polygons));
}
}
}
I have a Azure blob container which I am accessing using below code -
var blobContainerClient = GetBlobContainer(containerName);
if (blobContainerClient != null)
{
// List all blobs in the container
await foreach (BlobItem blobItem in blobContainerClient.GetBlobsAsync())
{
queuedBlobsList.Add(new QueuedBlobs { BlobName = blobItem.Name, LastModified = blobItem.Properties.LastModified });
}
}
private BlobContainerClient GetBlobContainer(string containerName)
{
return gen2StorageClient != null
? gen2StorageClient.GetBlobContainerClient(containerName)
: gen1StorageClient.GetBlobContainerClient(containerName);
}
The clients are initialised in constructor -
public class BlobService : IBlobService
{
private readonly BlobServiceClient gen1StorageClient, gen2StorageClient;
public BlobService(BlobServiceClient defaultClient, IAzureClientFactory<BlobServiceClient> clientFactory)
{
gen1StorageClient = defaultClient;
if (clientFactory != null)
{
gen2StorageClient = clientFactory.CreateClient("StorageConnectionString");
}
}
}
And my unit test where I am setting GetBlobsAsync is like this -
But I want to add list of BlobItems to test another loop.
private static Mock<BlobContainerClient> GetBlobContainerClientMockWithListOfBlobs()
{
var blobContainerClientMock = new Mock<BlobContainerClient>("UseDevelopmentStorage=true", EnvironmentConstants.ParallelUploadContainer);
var cancellationToken = new CancellationToken();
var blobs = new List<BlobItem>();
//AsyncPageable<BlobItem> blobItems = new AsyncPageable<BlobItem>(); -- Not allowing
blobContainerClientMock.Setup(x => x.GetBlobsAsync(BlobTraits.All, BlobStates.All, null, cancellationToken)).Returns(It.IsAny<AsyncPageable<BlobItem>>());
return blobContainerClientMock;
}
I came to this question because I also had the same issue.
Based on this article
AsyncPageable<T> and Pageable<T> are classes that represent collections of models returned by the service in pages.
The method GetBlobsAsync returns an AsyncPageable.
To Create an AsyncPageable you need first to create a BlobItem Page.
To create a Page<T> instance, use the Page<T>.FromValues method, passing a list of items, a continuation token, and the Response.
So let's start creating the list of items:
var blobList = new BlobItem[]
{
BlobsModelFactory.BlobItem("Blob1"),
BlobsModelFactory.BlobItem("Blob2"),
BlobsModelFactory.BlobItem("Blob3")
};
Note: BlobItem has an internal constructor but I found in this answer that there's a BlobsModelFactory.
After having the list of blobs is time to create a Page<BlobItem>:
Page<BlobItem> page = Page<BlobItem>.FromValues(blobList, null, Mock.Of<Response>());
And finally, create the AsyncPageable<BlobItem>
AsyncPageable<BlobItem> pageableBlobList = AsyncPageable<BlobItem>.FromPages(new[] { page });
And now you are able to use this to mock GetBlobsAsync method:
blobContainerClientMock
.Setup(m => m.GetBlobsAsync(
It.IsAny<BlobTraits>(),
It.IsAny<BlobStates>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.Returns(pageableBlobList);
I hope this helps others with this issue.
André
I’m writing a customization to add records to a timecard and I’m trying to create a new record to add to the timecard. Using the logic in T230 I’m creating a variable and I’m being told by the compiler that EPTimecardDetail cannot be found.
I’ve added using PX.Objects.EP and PX.Objects.PM but I figure that if TimeCardMaint can be found then EPTimecardDetail should be able be found as well. I’ve included my using code as well but I think I’m missing something else.
using System;
using System.Collections;
using PX.Data;
using PX.Data.BQL.Fluent;
using PX.Data.BQL;
using PX.Objects.CS;
using PX.Objects.PM;
using PX.Objects.EP;
using PX.Objects.CR;
using PX.Objects.AR;
using PX.Objects.CT;
using PX.Objects.GL.FinPeriods;
using PX.TM;
using System.Collections.Generic;
namespace TimecardImport
{
public class NLTimecardLineEntry : PXGraph<NLTimecardLineEntry>
{
private static void DoPopulateTimeCard(Int32 employeeID, DateTime startDate, NLTimecardLine record)
{
TimeCardMaint graph = PXGraph.CreateInstance<TimeCardMaint>();
Int32 cardWeekID = PXWeekSelector2Attribute.GetWeekID(graph, startDate);
//look for an employee timecard with the current weekID
EPTimeCard card = PXSelectReadonly<EPTimeCard,
Where<EPTimeCard.employeeID, Equal<Required<EPTimeCard.employeeID>>,
And<EPTimeCard.weekId, Equal<Required<EPTimeCard.weekId>>>>>.SelectWindowed(graph, 0, 1, employeeID, cardWeekID);
if (card == null) //if a card was not found, create one
{
card = (EPTimeCard)graph.Document.Cache.CreateInstance();
card.WeekID = cardWeekID;
card.EmployeeID = employeeID;
card = graph.Document.Insert(card);
}
//at this point card is the card that we're going to work with
var detailLine = (EPTimecardDetail)graph.Activities.Cache.CreateCopy(
graph.Activities.Insert());
//detailLine.SetValueExt<detailLine.Date_Date>(record, record.InDate);
//detailLine.EarningTypeID = "RG";
//detailLine = graph.Activities.Update(detailLine);
graph.Save.Press();
}
}}
The error I'm getting is "The type or namespace name 'EPTimecardDetail' could not be found (are you missing a using directive or an assembly reference?)".
EPTimecardDetail is defined within PX.Objects.EP so I'm not sure why I'm having a problem there. Or, perhaps this is not the way to add records to the Details tab of the Employee Time Card screen?
For the namespace issue you can declare using PX.Object.EP and refer to the type as TimeCardMaint.EPTimecardDetail
Or you can declare using static PX.Objects.EP.TimeCardMaint and refer to the type as EPTimecardDetail
For inserting the record check the source code in file TimeCardMaint.cs There are examples on how to insert this DAC record.
Make sure the fields used for SQL joins like OrigNoteID and RefNoteID have the proper value (non null).
This example is from the Correct action in TimeCardMaint:
[PXUIField(DisplayName = Messages.Correct)]
[PXButton(ImageKey = PX.Web.UI.Sprite.Main.Release)]
public virtual IEnumerable Correct(PXAdapter adapter)
{
if (Document.Current != null)
{
EPTimeCard source = GetLastCorrection(Document.Current);
if (source.IsReleased != true)
return new EPTimeCard[] { source };
EPTimeCard newCard = (EPTimeCard)Document.Cache.CreateInstance();
newCard.WeekID = source.WeekID;
newCard.OrigTimeCardCD = source.TimeCardCD;
newCard = Document.Insert(newCard);
newCard.EmployeeID = source.EmployeeID;
PXNoteAttribute.CopyNoteAndFiles(Document.Cache, source, Document.Cache, newCard, true, true);
bool failed = false;
Dictionary<string, TimeCardSummaryCopiedInfo> summaryDescriptions = new Dictionary<string, TimeCardSummaryCopiedInfo>();
foreach (EPTimeCardSummary summary in Summary.View.SelectMultiBound(new object[] { source }))
{
string key = GetSummaryKey(summary);
if (!summaryDescriptions.ContainsKey(key))
{
string note = PXNoteAttribute.GetNote(Summary.Cache, summary);
var info = new TimeCardSummaryCopiedInfo(summary.Description, note);
summaryDescriptions.Add(key, info);
}
}
foreach (EPTimecardDetail act in TimecardActivities.View.SelectMultiBound(new object[] { source }))
{
EPTimecardDetail newActivity = PXCache<EPTimecardDetail>.CreateCopy(act);
newActivity.Released = false;
newActivity.Billed = false;
newActivity.NoteID = null;
newActivity.TimeCardCD = null;
newActivity.TimeSheetCD = null;
newActivity.OrigNoteID = act.NoteID; //relation between the original activity and the corrected one.
newActivity.Date = act.Date;
newActivity.Billed = false;
newActivity.SummaryLineNbr = null;
newActivity.NoteID = null;
newActivity.ContractCD = null;
isCreateCorrectionFlag = true;
try
{
newActivity = Activities.Insert(newActivity);
}
catch (PXSetPropertyException ex)
{
failed = true;
Activities.Cache.RaiseExceptionHandling<EPTimecardDetail.summary>(act, act.Summary, new PXSetPropertyException(ex.MessageNoPrefix, PXErrorLevel.RowError));
continue;
}
newActivity.TrackTime = act.TrackTime; //copy as is.
isCreateCorrectionFlag = false;
newActivity.ApprovalStatus = ActivityStatusAttribute.Completed;
newActivity.RefNoteID = act.NoteID == act.RefNoteID ? newActivity.NoteID : act.RefNoteID;
newActivity.ContractCD = act.ContractCD;
PXNoteAttribute.CopyNoteAndFiles(Activities.Cache, act, Activities.Cache, newActivity);
Activities.Cache.SetValue<EPTimecardDetail.isCorrected>(act, true);
Activities.Cache.SetStatus(act, PXEntryStatus.Updated);
}
if (failed)
{
throw new PXException(Messages.FailedToCreateCorrectionTC);
}
foreach (EPTimeCardItem item in Items.View.SelectMultiBound(new object[] { source }))
{
EPTimeCardItem record = Items.Insert();
record.ProjectID = item.ProjectID;
record.TaskID = item.TaskID;
record.Description = item.Description;
record.InventoryID = item.InventoryID;
record.CostCodeID = item.CostCodeID;
record.UOM = item.UOM;
record.Mon = item.Mon;
record.Tue = item.Tue;
record.Wed = item.Wed;
record.Thu = item.Thu;
record.Fri = item.Fri;
record.Sat = item.Sat;
record.Sun = item.Sun;
record.OrigLineNbr = item.LineNbr;//relation between the original activity and the corrected one.
}
foreach (EPTimeCardSummary summary in Summary.Select())
{
string key = GetSummaryKey(summary);
if (summaryDescriptions.ContainsKey(key))
{
PXNoteAttribute.SetNote(Summary.Cache, summary, summaryDescriptions[key].Note);
Summary.Cache.SetValue<EPTimeCardSummary.description>(summary, summaryDescriptions[key].Description);
}
}
return new EPTimeCard[] { newCard };
}
return adapter.Get();
}
I have a project in Azure Devops with underlying repository as git. I have a automatically created database documentation which is stored in the project repository. To keep this documentation up-to date I want to schedule an application to push the generated documentation to azure on daily basis.
Basically, check out the file, write new content & check-in. Can we do this using Azure devops rest APIs? Is there any example code that I can follow?
You can have a scheduled build using Azure Pipelines, and in the build definition you define a Powershell script that run git related git commands as ShaykiAbramczyk suggested.
Need pay attention with below if you want to run Git commands in a script:
Grant version control permissions to the build service
Allow scripts to access the system token
Merge a feature branch to master
More details please refer our official doc here-- Run Git commands in a script
Script snippet:
#Config Set
git config user.email "$(Build.RequestedForEmail)"
git config user.name "$(Build.RequestedFor)"
#Push new Branch
git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push origin master:refs/heads/my-branch
#Other command
......
This is how I have implemented the a solution to check-in content in Azure Devops Git Repo.
Below is the generic class & caller method.
class AzureDevops
{
private readonly Uri uri;
private readonly string personalAccessToken;
public AzureDevops(string orgName, string personalAccessToken)
{
this.uri = new Uri("https://dev.azure.com/" + orgName);
this.personalAccessToken = personalAccessToken;
}
public T Post<T>(dynamic body, string path)
{
if (body == null)
throw new ArgumentNullException("body");
if (path == null)
throw new ArgumentNullException("path");
T output = default(T);
using (var client = new HttpClient())
{
client.BaseAddress = uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", personalAccessToken);
string serl_body = JsonConvert.SerializeObject(body);
var content = new StringContent(serl_body, Encoding.UTF8, "application/json");
using (HttpResponseMessage response = client.PostAsync(path, content).Result)
{
response.EnsureSuccessStatusCode();
output = response.Content.ReadAsAsync<T>().Result;
}
}
return output;
}
public T Get<T>(string path)
{
if (path == null)
throw new ArgumentNullException("path");
T output = default(T);
using (var client = new HttpClient())
{
client.BaseAddress = uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", personalAccessToken);
HttpResponseMessage response = client.GetAsync(path).Result;
if (response.IsSuccessStatusCode)
output = response.Content.ReadAsAsync<T>().Result;
else
throw new ApplicationException(string.Format("Response message is not OK. Issues in action: {0}", path));
}
return output;
}
}
public class Main
{
AzureDevops azureDevops = new AzureDevops("OrgName", "PAT");
private void AddNewContent()
{
ListOfRefResponse.Root listOfRefResponse = azureDevops.Get<ListOfRefResponse.Root>(string.Format("{0}/_apis/git/repositories/{1}/refs? api-version=6.0-preview.1&filter=heads/master", "projectId", "repositoryId"));
ArrayList contentArray = new ArrayList();
contentArray.Add(new ChangesBO
{
changeType = "add",
item = new ChangeItemBO { path = string.Concat("/", Constants.BaseAzureFolder, "/", "projedctName" + "/" + "filename.md") },
newContent = new ChangeContent { content = "new text content", contentType = "rawtext" }
});
dynamic body = new
{
refUpdates = new[] { new { name = Constants.Branch, oldObjectId = listOfRefResponse.value.First().objectId } },
commits = new[] {
new {
comment = Constants.AppKeysUpdateComment,
changes = contentArray.ToArray()
}}
};
CommitSuccessBO.Root commitSuccess = azureDevops.Post<CommitSuccessBO.Root>(body, string.Format("_apis/git/repositories/{0}/pushes?api-version=5.0", "RepositoryId"));
}
}
I am using Azure Automation Management 2.0.1. I am not able find Start method for Runbooks to execute a runbook. How do this with 2.0.1
var client = new Microsoft.Azure.Management.Automation.AutomationManagementClient(new CertificateCloudCredentials(subscriptionId, cert));
var ct = new CancellationToken();
var content = await client.Runbooks.ListByNameAsync("MyAutomationAccountName", "MyRunbookName", ct);
var firstOrDefault = content?.Runbooks.FirstOrDefault();
if (firstOrDefault != null)
{
var operation = client.Runbooks.Start("MyAutomationAccountName", new RunbookStartParameters(firstOrDefault.Id));
}
You need to use the automationManagementClient.Jobs.Create
public static JobCreateResponse Create(
this IJobOperations operations,
string resourceGroupName,
string automationAccount,
JobCreateParameters parameters
)
You can find a full sample here this would be the relevant part -
private void JobStart_Click(object sender, RoutedEventArgs e)
{
// Check for runbook name
if (String.IsNullOrWhiteSpace(RunbookName.Text) || String.IsNullOrWhiteSpace(PublishState.Text)) throw new ArgumentNullException(RunbookName.Text);
// Create job create parameters
var jcparam = new JobCreateParameters
{
Properties = new JobCreateProperties
{
Runbook = new RunbookAssociationProperty
{
// associate the runbook name
Name = RunbookName.Text
},
// pass parameters to runbook if any
Parameters = null
}
};
// create runbook job. This gives back JobId
var job = automationManagementClient.Jobs.Create(this.automationAccountName, jcparam).Job;
JobGuid.Text = JobId.Text = job.Properties.JobId.ToString();
Log.Text += (String.Format("\nJob Started for Runbook {0} , JobId {1}", RunbookName.Text, JobId.Text));
}