tools/pipeline-generator/Azure.Sdk.Tools.PipelineGenerator/PipelineGenerationContext.cs (251 lines of code) (raw):
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.TeamFoundation.Build.WebApi;
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.TeamFoundation.DistributedTask.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.ServiceEndpoints.WebApi;
using Microsoft.VisualStudio.Services.WebApi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using ServiceEndpoint = Microsoft.VisualStudio.Services.ServiceEndpoints.WebApi.ServiceEndpoint;
namespace PipelineGenerator
{
public class PipelineGenerationContext
{
private string organization;
private string project;
private string endpoint;
private string agentPool;
private int[] variableGroups;
private string devOpsPath;
public PipelineGenerationContext(
ILogger logger,
string organization,
string project,
string endpoint,
string repository,
string branch,
string agentPool,
int[] variableGroups,
string devOpsPath,
string prefix,
bool whatIf,
bool noSchedule,
bool setManagedVariables,
bool overwriteTriggers)
{
this.logger = logger;
this.organization = organization;
this.project = project;
this.endpoint = endpoint;
this.Repository = repository;
this.Branch = branch;
this.agentPool = agentPool;
this.variableGroups = variableGroups;
this.devOpsPath = devOpsPath;
this.Prefix = prefix;
this.WhatIf = whatIf;
this.NoSchedule = noSchedule;
this.SetManagedVariables = setManagedVariables;
this.OverwriteTriggers = overwriteTriggers;
}
public string Repository { get; }
public string Branch { get; }
public string Prefix { get; }
public bool WhatIf { get; }
public bool NoSchedule { get; }
public bool OverwriteTriggers { get; }
public bool SetManagedVariables { get; set; }
public int[] VariableGroups => this.variableGroups;
public string DevOpsPath => string.IsNullOrEmpty(this.devOpsPath) ? Prefix : this.devOpsPath;
private VssConnection cachedConnection;
private TokenCredential GetAzureCredentials()
{
return new ChainedTokenCredential(
new AzureCliCredential(),
new AzurePowerShellCredential()
);
}
private async Task<VssConnection> GetConnectionAsync()
{
if (cachedConnection == null)
{
var azureCredential = GetAzureCredentials();
var devopsCredential = new VssAzureIdentityCredential(azureCredential);
cachedConnection = new VssConnection(new Uri(organization), devopsCredential);
await cachedConnection.ConnectAsync();
}
return cachedConnection;
}
private ProjectHttpClient cachedProjectClient;
private async Task<ProjectHttpClient> GetProjectClientAsync(CancellationToken cancellationToken)
{
if (cachedProjectClient == null)
{
var connection = await GetConnectionAsync();
cachedProjectClient = await connection.GetClientAsync<ProjectHttpClient>(cancellationToken);
}
return cachedProjectClient;
}
private TeamProjectReference cachedProjectReference;
public async Task<TeamProjectReference> GetProjectReferenceAsync(CancellationToken cancellationToken)
{
if (cachedProjectReference == null)
{
var projectClient = await GetProjectClientAsync(cancellationToken);
this.logger.LogDebug("Getting projects from projectClient");
var projects = await projectClient.GetProjects(ProjectState.WellFormed);
this.logger.LogDebug("projectClient returned {Count} projects", projects.Count);
cachedProjectReference = projects.Single(p => p.Name.Equals(project, StringComparison.OrdinalIgnoreCase));
this.logger.LogDebug("Cached project {Name} with id {Id}", cachedProjectReference.Name, cachedProjectReference.Id);
}
return cachedProjectReference;
}
private ServiceEndpointHttpClient cachedServiceEndpointClient;
public async Task<ServiceEndpointHttpClient> GetServiceEndpointClientAsync(CancellationToken cancellationToken)
{
if (cachedServiceEndpointClient == null)
{
var connection = await GetConnectionAsync();
cachedServiceEndpointClient = await connection.GetClientAsync<ServiceEndpointHttpClient>(cancellationToken);
}
return cachedServiceEndpointClient;
}
private Microsoft.VisualStudio.Services.ServiceEndpoints.WebApi.ServiceEndpoint cachedServiceEndpoint;
public async Task<ServiceEndpoint> GetServiceEndpointAsync(CancellationToken cancellationToken)
{
if (cachedServiceEndpoint == null)
{
var serviceEndpointClient = await GetServiceEndpointClientAsync(cancellationToken);
var projectReference = await GetProjectReferenceAsync(cancellationToken);
this.logger.LogDebug("Getting service endpoints from serviceEndpointClient with endpoint name {EndpointName}", endpoint);
var serviceEndpoints = await serviceEndpointClient.GetServiceEndpointsByNamesAsync(
projectReference.Id.ToString(),
new [] { endpoint },
cancellationToken: cancellationToken
);
this.logger.LogDebug("serviceEndpointClient returned {Count} service endpoints", serviceEndpoints.Count);
cachedServiceEndpoint = serviceEndpoints.First();
this.logger.LogDebug("Cached service endpoint {Name} with id {Id}", cachedServiceEndpoint.Name, cachedServiceEndpoint.Id);
}
return cachedServiceEndpoint;
}
public async Task<IEnumerable<ServiceEndpoint>> GetServiceConnectionsAsync(IEnumerable<string> serviceConnections, CancellationToken cancellationToken)
{
var serviceEndpointClient = await GetServiceEndpointClientAsync(cancellationToken);
var projectReference = await GetProjectReferenceAsync(cancellationToken);
var allServiceConnections = await serviceEndpointClient.GetServiceEndpointsAsync(projectReference.Id.ToString(), cancellationToken: cancellationToken);
this.logger.LogDebug("Returned a total of {Count} service endpoints", allServiceConnections.Count);
List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
foreach (var serviceConnection in allServiceConnections)
{
if (serviceConnections.Contains(serviceConnection.Name))
{
endpoints.Add(serviceConnection);
}
}
return endpoints;
}
private HttpClient cachedRawHttpClient = null;
private async Task<HttpClient> GetRawHttpClient(CancellationToken cancellationToken)
{
if (this.cachedRawHttpClient == null)
{
var credential = GetAzureCredentials();
// Get token for Azure DevOps
var tokenRequestContext = new TokenRequestContext(new[] { "499b84ac-1321-427f-aa17-267ca6975798/.default" });
var accessToken = await credential.GetTokenAsync(tokenRequestContext, cancellationToken);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Token);
this.cachedRawHttpClient = client;
}
return this.cachedRawHttpClient;
}
private string GetPipelinePermissionsUrlForServiceConnections(Guid serviceConnectionId)
{
var apiVersion = "7.1-preview.1";
// https://learn.microsoft.com/en-us/rest/api/azure/devops/approvalsandchecks/pipeline-permissions/update-pipeline-permisions-for-resource?view=azure-devops-rest-7.1&tabs=HTTP
return $"{this.organization}/{this.project}/_apis/pipelines/pipelinepermissions/endpoint/{serviceConnectionId}?api-version={apiVersion}";
}
public async Task<JsonNode> GetPipelinePermissionsAsync(Guid serviceConnectionId, CancellationToken cancellationToken)
{
var url = GetPipelinePermissionsUrlForServiceConnections(serviceConnectionId);
var client = await GetRawHttpClient(cancellationToken);
var response = await client.GetAsync(url, cancellationToken);
if (response.IsSuccessStatusCode)
{
return JsonNode.Parse(await response.Content.ReadAsStringAsync());
}
else
{
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
throw new Exception($"GetPipelinePermissionsAsync throw an error [{response.StatusCode}]: {responseContent}");
}
}
public async Task UpdatePipelinePermissionsAsync(Guid serviceConnectionId, JsonNode serviceConnectionPermissions, CancellationToken cancellationToken)
{
var url = GetPipelinePermissionsUrlForServiceConnections(serviceConnectionId);
var client = await GetRawHttpClient(cancellationToken);
var request = new HttpRequestMessage(new HttpMethod("PATCH"), url)
{
Content = new StringContent(serviceConnectionPermissions.ToString(), Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
throw new Exception($"UpdatePipelinePermissionsAsync throw an error [{response.StatusCode}]: {responseContent}");
}
}
private BuildHttpClient cachedBuildClient;
public async Task<BuildHttpClient> GetBuildHttpClientAsync(CancellationToken cancellationToken)
{
if (cachedBuildClient == null)
{
var connection = await GetConnectionAsync();
cachedBuildClient = await connection.GetClientAsync<BuildHttpClient>(cancellationToken);
}
return cachedBuildClient;
}
private TaskAgentHttpClient cachedTaskAgentClient;
private async Task<TaskAgentHttpClient> GetTaskAgentClientAsync(CancellationToken cancellationToken)
{
if (cachedTaskAgentClient == null)
{
var connection = await GetConnectionAsync();
cachedTaskAgentClient = await connection.GetClientAsync<TaskAgentHttpClient>(cancellationToken);
}
return cachedTaskAgentClient;
}
private AgentPoolQueue cachedAgentPoolQueue;
private readonly ILogger logger;
public async Task<AgentPoolQueue> GetAgentPoolQueue(CancellationToken cancellationToken)
{
if (cachedAgentPoolQueue == null)
{
var projectReference = await GetProjectReferenceAsync(cancellationToken);
var taskAgentClient = await GetTaskAgentClientAsync(cancellationToken);
this.logger.LogDebug("Getting agent queues from taskAgentClient with queue name {QueueName}", agentPool);
var agentQueues = await taskAgentClient.GetAgentQueuesAsync(
project: projectReference.Id,
queueName: agentPool,
cancellationToken: cancellationToken
);
this.logger.LogDebug("taskAgentClient returned {Count} agent queues", agentQueues.Count);
cachedAgentPoolQueue = new AgentPoolQueue()
{
Id = agentQueues.First().Id
};
this.logger.LogDebug("Cached agent queue with id {Id}", cachedAgentPoolQueue.Id);
}
return cachedAgentPoolQueue;
}
}
}