Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions eng/common/pipelines/templates/steps/prepare-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,26 @@ steps:
--debug
${{parameters.TestsConventionOptions}}
displayName: Create Live Test pipelines for public repository
condition: ne('${{parameters.TestsConventionOptions}}','')
condition: and(succeeded(), ne('${{parameters.TestsConventionOptions}}',''))
env:
PATVAR: $(azuresdk-azure-sdk-devops-pipeline-generation-pat)
- script: >
$(Pipeline.Workspace)/pipeline-generator/pipeline-generator
--organization https://dev.azure.com/azure-sdk
--project internal
--prefix ${{parameters.Prefix}}
--devopspath "\${{parameters.Prefix}}"
--path $(System.DefaultWorkingDirectory)/sdk
--endpoint Azure
--repository ${{parameters.Repository}}
--convention weekly
--agentpool Hosted
--branch refs/heads/$(DefaultBranch)
--patvar PATVAR
--debug
${{parameters.TestsConventionOptions}}
displayName: Create Weekly (Multi-Cloud) Live Test pipelines for public repository
condition: and(succeeded(), ne('${{parameters.TestsConventionOptions}}',''))
env:
PATVAR: $(azuresdk-azure-sdk-devops-pipeline-generation-pat)

Expand Down Expand Up @@ -132,6 +151,6 @@ steps:
--no-schedule
${{parameters.TestsConventionOptions}}
displayName: Create Live Test pipelines for private repository
condition: ne('${{parameters.TestsConventionOptions}}','')
condition: and(succeeded(), ne('${{parameters.TestsConventionOptions}}',''))
env:
PATVAR: $(azuresdk-azure-sdk-devops-pipeline-generation-pat)
8 changes: 7 additions & 1 deletion eng/pipelines/pipeline-generation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,13 @@ jobs:
- script: |
$(Pipeline.Workspace)/pipeline-generator/pipeline-generator --organization https://dev.azure.com/azure-sdk --project internal --prefix $(Prefix) --path $(Pipeline.Workspace)/$(RepositoryName)/sdk --endpoint Azure --repository Azure/$(RepositoryName) --convention tests --agentpool Hosted --branch refs/heads/$(DefaultBranch) --patvar PATVAR --debug $(TestOptions)
displayName: 'Generate test pipelines for: $(RepositoryName)'
condition: ne(variables['TestOptions'],'')
condition: and(succeeded(), ne(variables['TestOptions'],''))
env:
PATVAR: $(azuresdk-azure-sdk-devops-pipeline-generation-pat)
- script: |
$(Pipeline.Workspace)/pipeline-generator/pipeline-generator --organization https://dev.azure.com/azure-sdk --project internal --prefix $(Prefix) --path $(Pipeline.Workspace)/$(RepositoryName)/sdk --endpoint Azure --repository Azure/$(RepositoryName) --convention weekly --agentpool Hosted --branch refs/heads/$(DefaultBranch) --patvar PATVAR --debug $(TestOptions)
displayName: 'Generate weekly test pipelines (multi-cloud) for: $(RepositoryName)'
condition: and(succeeded(), ne(variables['TestOptions'],''))
env:
PATVAR: $(azuresdk-azure-sdk-devops-pipeline-generation-pat)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>

<PackAsTool>true</PackAsTool>
<ToolCommandName>pipeline-generator</ToolCommandName>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public IntegrationTestingPipelineConvention(ILogger logger, PipelineGenerationCo
{
}

protected override string GetDefinitionName(SdkComponent component)
public override string GetDefinitionName(SdkComponent component)
{
return component.Variant == null ? $"{Context.Prefix} - {component.Name} - tests" : $"{Context.Prefix} - {component.Name} - tests.{component.Variant}";
}
Expand All @@ -25,7 +25,7 @@ protected override async Task<bool> ApplyConventionAsync(BuildDefinition definit
{
var hasChanges = await base.ApplyConventionAsync(definition, component);

if (EnsureDefautPullRequestTrigger(definition, overrideYaml: true, securePipeline: true))
if (EnsureDefaultPullRequestTrigger(definition, overrideYaml: true, securePipeline: true))
{
hasChanges = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public PipelineConvention(ILogger logger, PipelineGenerationContext context)

public abstract string SearchPattern { get; }

protected abstract string GetDefinitionName(SdkComponent component);
public abstract string GetDefinitionName(SdkComponent component);

public async Task<BuildDefinition> DeleteDefinitionAsync(SdkComponent component, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -80,7 +80,7 @@ public async Task<BuildDefinition> CreateOrUpdateDefinitionAsync(SdkComponent co
Logger.LogDebug("Applying convention to '{0}' definition.", definitionName);
var hasChanges = await ApplyConventionAsync(definition, component);

if (hasChanges)
if (hasChanges || Context.OverwriteTriggers)
{
if (!Context.WhatIf)
{
Expand Down Expand Up @@ -313,7 +313,7 @@ protected virtual Task<bool> ApplyConventionAsync(BuildDefinition definition, Sd
return Task.FromResult(hasChanges);
}

protected bool EnsureDefautPullRequestTrigger(BuildDefinition definition, bool overrideYaml = true, bool securePipeline = true)
protected bool EnsureDefaultPullRequestTrigger(BuildDefinition definition, bool overrideYaml = true, bool securePipeline = true)
{
bool hasChanges = false;
var prTriggers = definition.Triggers.OfType<PullRequestTrigger>();
Expand Down Expand Up @@ -349,23 +349,24 @@ protected bool EnsureDefautPullRequestTrigger(BuildDefinition definition, bool o
{
if (overrideYaml)
{
if (trigger.SettingsSourceType != 1 ||
trigger.BranchFilters.Contains("+*"))
// Override what is in the yaml file and use what is in the pipeline definition
if (trigger.SettingsSourceType != 1)
{
// Override what is in the yaml file and use what is in the pipeline definition
trigger.SettingsSourceType = 1;
trigger.BranchFilters.Add("+*");
hasChanges = true;
}
}
else
{
if (trigger.SettingsSourceType != 2)

if (!trigger.BranchFilters.Contains("+*"))
{
// Pull settings from yaml
trigger.SettingsSourceType = 2;
trigger.BranchFilters.Add("+*");
hasChanges = true;
}

}
else if (trigger.SettingsSourceType != 2)
{
// Pull settings from yaml
trigger.SettingsSourceType = 2;
hasChanges = true;
}
if (trigger.RequireCommentsForNonTeamMembersOnly != false ||
trigger.Forks.AllowSecrets != securePipeline ||
Expand All @@ -377,7 +378,7 @@ protected bool EnsureDefautPullRequestTrigger(BuildDefinition definition, bool o
trigger.Forks.Enabled = true;
trigger.RequireCommentsForNonTeamMembersOnly = false;
trigger.IsCommentRequiredForPullRequest = securePipeline;

hasChanges = true;
}
}
Expand All @@ -390,11 +391,12 @@ protected bool EnsureDefaultScheduledTrigger(BuildDefinition definition)
bool hasChanges = false;
var scheduleTriggers = definition.Triggers.OfType<ScheduleTrigger>();

// Only add the schedule trigger if one doesn't exist.
if (scheduleTriggers == default || !scheduleTriggers.Any())
// Only add the schedule trigger if one doesn't exist.
if (scheduleTriggers == default || !scheduleTriggers.Any() || Context.OverwriteTriggers)
{
var computedSchedule = CreateScheduleFromDefinition(definition);

definition.Triggers.RemoveAll(e => e is ScheduleTrigger);
definition.Triggers.Add(new ScheduleTrigger
{
Schedules = new List<Schedule> { computedSchedule }
Expand All @@ -409,8 +411,9 @@ protected bool EnsureDefaultCITrigger(BuildDefinition definition)
{
bool hasChanges = false;
var ciTrigger = definition.Triggers.OfType<ContinuousIntegrationTrigger>().SingleOrDefault();
if (ciTrigger == null)
if (ciTrigger == null || Context.OverwriteTriggers)
{
definition.Triggers.RemoveAll(e => e is ContinuousIntegrationTrigger);
definition.Triggers.Add(new ContinuousIntegrationTrigger()
{
SettingsSourceType = 2 // Get CI trigger data from yaml file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public PullRequestValidationPipelineConvention(ILogger logger, PipelineGeneratio
{
}

protected override string GetDefinitionName(SdkComponent component)
public override string GetDefinitionName(SdkComponent component)
{
return component.Variant == null ? $"{Context.Prefix} - {component.Name} - ci" : $"{Context.Prefix} - {component.Name} - ci.{component.Variant}";
}
Expand All @@ -25,7 +25,7 @@ protected override async Task<bool> ApplyConventionAsync(BuildDefinition definit
{
var hasChanges = await base.ApplyConventionAsync(definition, component);

if (EnsureDefautPullRequestTrigger(definition, overrideYaml: false, securePipeline: false))
if (EnsureDefaultPullRequestTrigger(definition, overrideYaml: false, securePipeline: false))
{
hasChanges = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public UnifiedPipelineConvention(ILogger logger, PipelineGenerationContext conte
{
}

protected override string GetDefinitionName(SdkComponent component)
public override string GetDefinitionName(SdkComponent component)
{
return component.Variant == null ? $"{Context.Prefix} - {component.Name}" : $"{Context.Prefix} - {component.Name} - {component.Variant}";
}
Expand All @@ -25,7 +25,7 @@ protected override async Task<bool> ApplyConventionAsync(BuildDefinition definit
{
var hasChanges = await base.ApplyConventionAsync(definition, component);

if (EnsureDefautPullRequestTrigger(definition, overrideYaml: true, securePipeline: true))
if (EnsureDefaultPullRequestTrigger(definition, overrideYaml: true, securePipeline: true))
{
hasChanges = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public WeeklyIntegrationTestingPipelineConvention(ILogger logger, PipelineGenera
{
}

protected override string GetDefinitionName(SdkComponent component)
public override string GetDefinitionName(SdkComponent component)
{
var definitionName = $"{Context.Prefix} - {component.Name} - tests-weekly";
if (component.Variant != null) {
Expand All @@ -23,10 +23,12 @@ protected override Schedule CreateScheduleFromDefinition(BuildDefinition definit
var bucket = definition.Id % TotalBuckets;
var startHours = bucket / BucketsPerHour;
var startMinutes = bucket % BucketsPerHour;
var daysToBuild = new ScheduleDays[]{ ScheduleDays.Saturday, ScheduleDays.Sunday };
var dayBucket = definition.Id % daysToBuild.Length;

var schedule = new Schedule
{
DaysToBuild = ScheduleDays.Saturday | ScheduleDays.Sunday,
DaysToBuild = daysToBuild[dayBucket],
ScheduleOnlyWithChanges = true,
StartHours = FirstSchedulingHour + startHours,
StartMinutes = startMinutes * BucketSizeInMinutes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum ExitCondition
Success = 0,
Exception = 1,
InvalidArguments = 2,
NoComponentsFound = 3
NoComponentsFound = 3,
DuplicateComponentsFound = 4
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ public class PipelineGenerationContext
private string devOpsPath;

public PipelineGenerationContext(
string organization,
string project,
string patvar,
string endpoint,
string repository,
string branch,
string agentPool,
string organization,
string project,
string patvar,
string endpoint,
string repository,
string branch,
string agentPool,
string[] variableGroups,
string devOpsPath,
string prefix,
string prefix,
bool whatIf,
bool noSchedule)
bool noSchedule,
bool overwriteTriggers)
{
this.organization = organization;
this.project = project;
Expand All @@ -51,16 +52,18 @@ public PipelineGenerationContext(
this.Prefix = prefix;
this.WhatIf = whatIf;
this.NoSchedule = noSchedule;
this.OverwriteTriggers = overwriteTriggers;
}

public string Branch { get; }
public string Prefix { get; }
public bool WhatIf { get; }
public bool NoSchedule { get; }
public bool OverwriteTriggers { get; }
public int[] VariableGroups => this.variableGroups;
public string DevOpsPath => string.IsNullOrEmpty(this.devOpsPath) ? Prefix : this.devOpsPath;

private int[] ParseIntArray(string[] strs)
private int[] ParseIntArray(string[] strs)
=> strs.Select(str => int.Parse(str)).ToArray();

private VssConnection cachedConnection;
Expand Down Expand Up @@ -91,7 +94,7 @@ private async Task<ProjectHttpClient> GetProjectClientAsync(CancellationToken ca
}

private TeamProjectReference cachedProjectReference;

public async Task<TeamProjectReference> GetProjectReferenceAsync(CancellationToken cancellationToken)
{
if (cachedProjectReference == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private static CommandLineApplication PrepareApplication(CancellationTokenSource
var destroyOption = app.Option("--destroy", "Use this switch to delete the pipelines instead (DANGER!)", CommandOptionType.NoValue);
var debugOption = app.Option("--debug", "Turn on debug level logging.", CommandOptionType.NoValue);
var noScheduleOption = app.Option("--no-schedule", "Don't create any scheduled triggers.", CommandOptionType.NoValue);
var overwriteTriggersOption = app.Option("--overwrite-triggers", "Overwrite existing pipeline triggers (triggers may be manually modified, use with caution).", CommandOptionType.NoValue);

app.OnExecute(() =>
{
Expand All @@ -83,6 +84,7 @@ private static CommandLineApplication PrepareApplication(CancellationTokenSource
openOption.HasValue(),
destroyOption.HasValue(),
noScheduleOption.HasValue(),
overwriteTriggersOption.HasValue(),
cancellationTokenSource.Token
).Result;

Expand Down Expand Up @@ -146,6 +148,7 @@ public async Task<ExitCondition> RunAsync(
bool open,
bool destroy,
bool noSchedule,
bool overwriteTriggers,
CancellationToken cancellationToken)
{
try
Expand All @@ -167,7 +170,8 @@ public async Task<ExitCondition> RunAsync(
devOpsPathValue,
prefix,
whatIf,
noSchedule
noSchedule,
overwriteTriggers
);

var pipelineConvention = GetPipelineConvention(convention, context);
Expand All @@ -180,6 +184,12 @@ public async Task<ExitCondition> RunAsync(
}

logger.LogInformation("Found {0} components", components.Count());

if (HasPipelineDefinitionNameDuplicates(pipelineConvention, components))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might cause our pipeline generation to start failing so you need to be careful when you enable it for the first time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it passes for all repos (up, ci, tests, weekly conventions). Minor issue with local copies of node_modules causing duplicates, but that's an edge case that I think is ok and won't affect the pipeline.

{
return ExitCondition.DuplicateComponentsFound;
}

foreach (var component in components)
{
logger.LogInformation("Processing component '{0}' in '{1}'.", component.Name, component.Path);
Expand Down Expand Up @@ -236,5 +246,36 @@ private IEnumerable<SdkComponent> ScanForComponents(string path, string searchPa
var components = scanner.Scan(scanDirectory, searchPattern);
return components;
}

private bool HasPipelineDefinitionNameDuplicates(PipelineConvention convention, IEnumerable<SdkComponent> components)
{
var pipelineNames = new Dictionary<string, SdkComponent>();
var duplicates = new HashSet<SdkComponent>();

foreach (var component in components)
{
var definitionName = convention.GetDefinitionName(component);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this causes a network call each time and we already do this in other places. Should we just add the dictionary building and checking there instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere in CreateOrUpdateDefinitionAsync?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetDefinitionName is just a string building function, for example:

return component.Variant == null ? $"{Context.Prefix} - {component.Name} - ci" : $"{Context.Prefix} - {component.Name} - ci.{component.Variant}";

It's less efficient to do a separate check here as opposed to adding this logic incrementally in the scan step, but I think it's simpler this way and won't have any noticeable impact.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK as long as we are just processing things in memory and not making more network calls I'm cool with making it an explicit check.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also explicitly did it here vs. per update call, because I wanted to be able to collect all collisions in case there are more than 2, so that we can fail with full information about what needs to be fixed.

if (pipelineNames.TryGetValue(definitionName, out var duplicate))
{
duplicates.Add(duplicate);
duplicates.Add(component);
}
else
{
pipelineNames.Add(definitionName, component);
}
}

if (duplicates.Count > 0) {
logger.LogError("Found multiple pipeline definitions that will result in name collisions. This can happen when nested directory names are the same.");
logger.LogError("Suggested fix: add a 'variant' to the yaml filename, e.g. 'sdk/keyvault/internal/ci.yml' => 'sdk/keyvault/internal/ci.keyvault.yml'");
var paths = duplicates.Select(d => $"'{d.RelativeYamlPath}'");
logger.LogError($"Pipeline definitions affected: {String.Join(", ", paths)}");

return true;
}

return false;
}
}
}