Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Serviços de DevOps do Azure | Azure DevOps Server | Azure DevOps Server 2022
Criar itens de trabalho programaticamente é um cenário de automação comum nos Serviços de DevOps do Azure. Este artigo mostra como criar um bug (ou qualquer item de trabalho) usando bibliotecas de cliente .NET com métodos de autenticação modernos.
Pré-requisitos
| Categoria | Requerimentos |
|---|---|
| Azure DevOps |
-
Uma organização - Acesso a um projeto onde você pode criar itens de trabalho |
| Autenticação | Escolha uma das seguintes opções: - Autenticação do Microsoft Entra ID (recomendado) - Token de Acesso Pessoal (PAT) (para teste) |
| Ambiente de desenvolvimento | Um ambiente de desenvolvimento C#. Você pode usar o Visual Studio |
Importante
Para aplicativos de produção, recomendamos o uso da autenticação Microsoft Entra ID em vez de Tokens de Acesso Pessoal. Os PATs são adequados para cenários de teste e desenvolvimento. Para obter orientação sobre como escolher o método de autenticação correto, consulte Diretrizes de autenticação.
Opções de autenticação
Este artigo demonstra vários métodos de autenticação para se adequar a diferentes cenários:
Autenticação do Microsoft Entra ID (recomendada para aplicativos de usuário)
Para aplicativos de produção com interação do usuário, use a autenticação Microsoft Entra ID:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
Autenticação da entidade de serviço (recomendada para automação)
Para cenários automatizados, pipelines de CI/CD e aplicativos de servidor:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
Autenticação de identidade gerenciada (recomendada para aplicativos hospedados no Azure)
Para aplicativos executados nos serviços do Azure (Funções, Serviço de Aplicativo, etc.):
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />
Autenticação de Token de Acesso Pessoal
Para cenários de desenvolvimento e teste:
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
Exemplos de código C#
Os exemplos a seguir mostram como criar itens de trabalho usando diferentes métodos de autenticação.
Exemplo 1: Autenticação de ID do Microsoft Entra (Interativo)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.VisualStudio.Services.InteractiveClient
// Microsoft.Identity.Client
using System;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class EntraIdBugCreator
{
private readonly Uri uri;
/// <summary>
/// Initializes a new instance using Microsoft Entra ID authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
public EntraIdBugCreator(string orgName)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
}
/// <summary>
/// Create a bug using Microsoft Entra ID authentication.
/// </summary>
/// <param name="project">The name of your project</param>
/// <param name="title">Bug title</param>
/// <param name="reproSteps">Reproduction steps</param>
/// <param name="priority">Priority level (1-4)</param>
/// <param name="severity">Severity level</param>
/// <returns>The created WorkItem</returns>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
// Use Microsoft Entra ID authentication
var credentials = new VssAadCredential();
var patchDocument = new JsonPatchDocument();
// Add required and optional fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var result = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{result.Id}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Exemplo 2: Autenticação do principal de serviço (cenários automatizados)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.Identity.Client
using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class ServicePrincipalBugCreator
{
private readonly Uri uri;
private readonly string clientId;
private readonly string clientSecret;
private readonly string tenantId;
/// <summary>
/// Initializes a new instance using Service Principal authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
/// <param name="clientId">Service principal client ID</param>
/// <param name="clientSecret">Service principal client secret</param>
/// <param name="tenantId">Azure AD tenant ID</param>
public ServicePrincipalBugCreator(string orgName, string clientId, string clientSecret, string tenantId)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenantId = tenantId;
}
/// <summary>
/// Create a bug using Service Principal authentication.
/// </summary>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
// Acquire token using Service Principal
var app = ConfidentialClientApplicationBuilder
.Create(this.clientId)
.WithClientSecret(this.clientSecret)
.WithAuthority($"https://login.microsoftonline.com/{this.tenantId}")
.Build();
var scopes = new[] { "https://app.vssps.visualstudio.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
var patchDocument = new JsonPatchDocument();
// Add work item fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var workItem = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{workItem.Id}");
return workItem;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Exemplo 3: Autenticação de identidade gerenciada (aplicativos hospedados no Azure)
// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Azure.Identity
using System;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class ManagedIdentityBugCreator
{
private readonly Uri uri;
/// <summary>
/// Initializes a new instance using Managed Identity authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
public ManagedIdentityBugCreator(string orgName)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
}
/// <summary>
/// Create a bug using Managed Identity authentication.
/// </summary>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
// Use Managed Identity to acquire token
var credential = new DefaultAzureCredential();
var tokenRequestContext = new TokenRequestContext(new[] { "https://app.vssps.visualstudio.com/.default" });
var tokenResult = await credential.GetTokenAsync(tokenRequestContext);
var credentials = new VssOAuthAccessTokenCredential(tokenResult.Token);
var patchDocument = new JsonPatchDocument();
// Add work item fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var workItem = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{workItem.Id}");
return workItem;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Exemplo 4: Autenticação de Token de Acesso Pessoal
// NuGet package: Microsoft.TeamFoundationServer.Client
using System;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
public class PatBugCreator
{
private readonly Uri uri;
private readonly string personalAccessToken;
/// <summary>
/// Initializes a new instance using Personal Access Token authentication.
/// </summary>
/// <param name="orgName">Your Azure DevOps organization name</param>
/// <param name="personalAccessToken">Your Personal Access Token</param>
public PatBugCreator(string orgName, string personalAccessToken)
{
this.uri = new Uri($"https://dev.azure.com/{orgName}");
this.personalAccessToken = personalAccessToken;
}
/// <summary>
/// Create a bug using Personal Access Token authentication.
/// </summary>
/// <param name="project">The name of your project</param>
/// <param name="title">Bug title</param>
/// <param name="reproSteps">Reproduction steps</param>
/// <param name="priority">Priority level (1-4)</param>
/// <param name="severity">Severity level</param>
/// <returns>The created WorkItem</returns>
public async Task<WorkItem> CreateBugAsync(string project, string title, string reproSteps, int priority = 2, string severity = "3 - Medium")
{
var credentials = new VssBasicCredential(string.Empty, this.personalAccessToken);
var patchDocument = new JsonPatchDocument();
// Add required and optional fields
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.Title",
Value = title
});
if (!string.IsNullOrEmpty(reproSteps))
{
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Value = reproSteps
});
}
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Priority",
Value = priority.ToString()
});
patchDocument.Add(new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/Microsoft.VSTS.Common.Severity",
Value = severity
});
using (var connection = new VssConnection(this.uri, new VssCredentials(credentials)))
{
var workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
try
{
var result = await workItemTrackingHttpClient.CreateWorkItemAsync(patchDocument, project, "Bug").ConfigureAwait(false);
Console.WriteLine($"Bug successfully created: Bug #{result.Id}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Error creating bug: {ex.Message}");
throw;
}
}
}
}
Exemplos de utilização
Usando a autenticação de ID do Microsoft Entra (Interativo)
class Program
{
static async Task Main(string[] args)
{
var bugCreator = new EntraIdBugCreator("your-organization-name");
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Authorization Errors with Microsoft Accounts",
reproSteps: "Our authorization logic needs to allow for users with Microsoft accounts (formerly Live IDs) - https://docs.microsoft.com/library/live/hh826547.aspx",
priority: 1,
severity: "2 - High"
);
Console.WriteLine($"Created bug with ID: {bug.Id}");
}
}
Usando a autenticação da entidade de serviço (cenários de CI/CD)
class Program
{
static async Task Main(string[] args)
{
// These values should come from environment variables or Azure Key Vault
var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
var bugCreator = new ServicePrincipalBugCreator("your-organization-name", clientId, clientSecret, tenantId);
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Automated Bug Report",
reproSteps: "Issue detected by automated testing...",
priority: 2,
severity: "3 - Medium"
);
Console.WriteLine($"Automated bug created: #{bug.Id}");
}
}
Usando a autenticação de identidade gerenciada (Azure Functions/Serviço de Aplicativo)
public class BugReportFunction
{
[FunctionName("CreateBugReport")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
var bugCreator = new ManagedIdentityBugCreator("your-organization-name");
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Function-detected Issue",
reproSteps: "Issue reported via Azure Function...",
priority: 3,
severity: "4 - Low"
);
return new OkObjectResult($"Bug created: {bug.Id}");
}
}
Utilizar autenticação por Token de Acesso Pessoal (Desenvolvimento/Teste)
class Program
{
static async Task Main(string[] args)
{
var pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT"); // Never hardcode PATs
var bugCreator = new PatBugCreator("your-organization-name", pat);
var bug = await bugCreator.CreateBugAsync(
project: "your-project-name",
title: "Sample Bug Title",
reproSteps: "Steps to reproduce the issue...",
priority: 2,
severity: "3 - Medium"
);
Console.WriteLine($"Bug created successfully: #{bug.Id}");
}
}
Referência de campo de tarefa
Ao criar itens de trabalho, você normalmente usará estes campos:
Campos obrigatórios
- System.Title: O título do item de trabalho (necessário para todos os tipos de item de trabalho)
- System.WorkItemType: Definido automaticamente ao especificar o tipo na chamada de API
Campos opcionais comuns
- Microsoft.VSTS.TCM.ReproSteps: Etapas detalhadas de reprodução
- Microsoft.VSTS.Common.Priority: Nível de prioridade (1=mais alto, 4=mais baixo)
- Microsoft.VSTS.Common.Severity: Classificação de gravidade
- System.Description: Descrição geral ou detalhes adicionais
- System.AssignedTo: Pessoa responsável pelo item de trabalho
- System.AreaPath: Classificação de área
- System.IterationPath: Atribuição de iteração/sprint
Valores de prioridade
- 1: Prioridade crítica/máxima
- 2: Alta prioridade
- 3: Prioridade média (padrão)
- 4: Baixa prioridade
Valores comuns de gravidade
- 1 - Crítico: Sistema inutilizável, bloqueando o progresso
- 2 - Alta: Funcionalidade principal quebrada
- 3 - Médio: Algumas funcionalidades quebradas (padrão)
- 4 - Baixo: Questões menores ou problemas estéticos
Melhores práticas
Autenticação
- Usar o Microsoft Entra ID para aplicativos interativos com entrada do usuário
- Use o Principal de Serviço para cenários automatizados, pipelines de CI/CD e aplicações de servidor
- Usar a Identidade Gerenciada para aplicativos executados nos serviços do Azure (Funções, Serviço de Aplicativo, VMs)
- Evite Tokens de Acesso Pessoal em produção; Uso apenas para desenvolvimento e testes
- Nunca codifice credenciais no código-fonte; usar variáveis de ambiente ou o Azure Key Vault
- Implementar rotação de credenciais para aplicativos de longa execução
- Garantir escopos adequados: a criação de item de trabalho requer permissões apropriadas no Azure DevOps
Tratamento de erros
- Implementar o tratamento adequado de exceções para falhas de autenticação e API
- Validar valores de campo antes de tentar criar itens de trabalho
- Manipular erros de validação de campo retornados pela API
- Use padrões async/await para melhorar a capacidade de resposta do aplicativo
Desempenho
- Operações em lote ao criar vários itens de trabalho
- Conexões de cache ao fazer várias chamadas de API
- Use valores de tempo limite apropriados para operações de longa duração
- Implementar lógica de nova tentativa com backoff exponencial para falhas transitórias
Validação de dados
- Validar campos obrigatórios antes de chamadas de API
- Verificar permissões de campo e regras de tipo de item de trabalho
- Sanear a entrada do utilizador para evitar ataques de injeção
- Siga os requisitos de campo específicos do projeto e as convenções de nomenclatura
Solução de problemas
Problemas de autenticação
- Falhas de autenticação do Microsoft Entra ID: verifique se o usuário tem as permissões adequadas para criar itens de trabalho
- Falhas de autenticação da entidade de serviço: verifique se a ID do cliente, o segredo e a ID do locatário estão corretos; verificar as permissões da entidade de serviço no Azure DevOps
- Falhas de autenticação de Identidade Gerenciada: verifique se o recurso do Azure tem uma identidade gerenciada habilitada e permissões adequadas
-
Falhas de autenticação PAT: verifique se o token tem
vso.work_writeescopo e não expirou - 403 Erros proibidos: Verifique as permissões do projeto e o acesso ao tipo de item de trabalho
Erros de validação de campo
- Campo obrigatório ausente: verifique se todos os campos obrigatórios estão incluídos no documento de patch
- Valores de campo inválidos: verifique se os valores de campo correspondem ao formato esperado e aos valores permitidos
- Campo não encontrado: Verifique se os nomes dos campos estão escritos corretamente e existem para o tipo de item de trabalho
- Erros de campo somente leitura: alguns campos não podem ser definidos durante a criação (por exemplo, System.CreatedBy)
Exceções comuns
- VssUnauthorizedException: Falha na autenticação ou permissões insuficientes
- VssServiceException: erros de validação do lado do servidor ou problemas de API
- ArgumentException: Parâmetros inválidos ou documento de patch malformado
- JsonReaderException: Problemas com serialização/desserialização JSON
Problemas de desempenho
- Respostas lentas da API: verifique a conectividade de rede e o status do serviço Azure DevOps
- Uso de memória: descarte conexões e clientes corretamente
- Rate limiting: implemente os atrasos apropriados entre chamadas de API