Skip to content

Commit

Permalink
Initial code.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kolky committed Nov 24, 2020
1 parent 19b049a commit ef30335
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 1 deletion.
25 changes: 25 additions & 0 deletions DevOpsArtifactDownloader.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30204.135
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevOpsArtifactDownloader", "DevOpsArtifactDownloader\DevOpsArtifactDownloader.csproj", "{79E8A515-9079-46BD-9034-E49E823E5F30}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{79E8A515-9079-46BD-9034-E49E823E5F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79E8A515-9079-46BD-9034-E49E823E5F30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79E8A515-9079-46BD-9034-E49E823E5F30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79E8A515-9079-46BD-9034-E49E823E5F30}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C6561EFB-5086-4ECD-82AC-7786FEE7A208}
EndGlobalSection
EndGlobal
27 changes: 27 additions & 0 deletions DevOpsArtifactDownloader/DevOpsArtifactDownloader.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Authors>Alexander van der Kolk</Authors>
<Company>Kolky.nl</Company>
<Description>Tool to download the Latest Build Artifact from Azure DevOps.</Description>
<Copyright>© 2020 Kolky.nl</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/Kolky/DevOpsArtifactDownloader</PackageProjectUrl>
<RepositoryUrl>https://github.com/Kolky/DevOpsArtifactDownloader</RepositoryUrl>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="16.153.0" />
</ItemGroup>

<ItemGroup>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>

</Project>
111 changes: 111 additions & 0 deletions DevOpsArtifactDownloader/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommandLine;
using Microsoft.TeamFoundation.Build.WebApi;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

namespace DevOpsArtifactDownloader
{
internal class Program
{
private enum ExitCode : int
{
Success = 0,
NoBuildsAvailable = 1,
SavingResultFailed = 2,
}

private class Options
{
[Option('o', "organization", Required = true, HelpText = "Your Azure DevOps organization.")]
public string Organization { get; set; }

[Option('t', "pat", Required = true, HelpText = "Your Personal Access Token.")]
public string PersonalAccessToken { get; set; }

[Option('p', "project", Required = true, HelpText = "The specified project.")]
public string Project { get; set; }

[Option('d', "definition", Required = true, HelpText = "The pipeline definition-id, get it from the browser url.")]
public int Defintion { get; set; }

[Option('b', "branch", HelpText = "The specified branch, use git refs format. For example: /refs/heads/develop")]
public string Branch { get; set; }

[Option('a', "artifact", Required = true, HelpText = "The specified artifact name. Use {buildId}, {buildNumber} or {revision} to replace with associated details of the build.")]
public string Artifact { get; set; }

[Option('r', "result", Default = "Build.zip", HelpText = "The output file name, must end in '.zip'. Use {buildId}, {buildNumber} or {revision} to replace with associated details of the build.")]
public string Result { get; set; }
}

private static async Task Main(string[] args)
{
await Parser.Default.ParseArguments<Options>(args)
.WithParsedAsync(Execute);
}

private static async Task Execute(Options options)
{
// Create instance of VssConnection using Personal Access Token
var connection = new VssConnection(new Uri($"https://dev.azure.com/{options.Organization}"), new VssBasicCredential(string.Empty, options.PersonalAccessToken));
var client = await connection.GetClientAsync<BuildHttpClient>();

// Find latest build for project, definition and branch.
var builds = await client.GetBuildsAsync(
options.Project,
definitions: new[] { options.Defintion },
branchName: options.Branch,
statusFilter: BuildStatus.Completed,
resultFilter: BuildResult.Succeeded,
queryOrder: BuildQueryOrder.FinishTimeDescending,
top: 1);
if (!builds.Any())
{
Environment.Exit((int)ExitCode.NoBuildsAvailable);
return;
}

var build = builds.Single();

try
{
using (var zip = await client.GetArtifactContentZipAsync(options.Project, build.Id, BuildArtifactName(options.Artifact, build)))
using (var fileStream = File.OpenWrite(BuildResultFile(options.Result, build)))
{
await zip.CopyToAsync(fileStream);
}
}
catch (IOException)
{
Environment.Exit((int)ExitCode.SavingResultFailed);
}
}

private static string BuildArtifactName(string artifactName, Build build)
{
return artifactName
.Replace("{buildId}", build.Id.ToString())
.Replace("{buildNumer}", build.BuildNumber)
.Replace("{revision}", build.BuildNumberRevision?.ToString() ?? string.Empty);
}

private static string BuildResultFile(string result, Build build)
{
if (string.IsNullOrWhiteSpace(result))
{
return "Build.zip";
}

if (!result.EndsWith(".zip"))
{
return $"{BuildArtifactName(result, build)}.zip";
}

return BuildArtifactName(result, build);
}
}
}
8 changes: 8 additions & 0 deletions DevOpsArtifactDownloader/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"DevOpsArtifactDownloader": {
"commandName": "Project",
"commandLineArgs": "-o *organization* -t *accesstoken* -p *project* -d 5 -b \"refs/heads/develop\" -a \"*project* #{buildId}\" -r Build.zip"
}
}
}
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
# DevOpsArtifactDownloader
Tool to download the Latest Build Artifact from Azure DevOps
Tool to download the Latest Build Artifact from Azure DevOps.

## Why?
With this tool you can use other tools developed in Azure DevOps on a private Jenkins behind a VPN connection.

Use it to download your latest DevOps build artifacts through a Jenkins job. Which can be triggered on a successfull build by a webhook. The webhook can be sent to a public or private [smee.io](https://smee.io/) url which a local smee-client then passes to a jenkins server. Jenkins can trigger a job using the [Generic Webhook Trigger](https://plugins.jenkins.io/generic-webhook-trigger/) plugin. You could then make your downloaded DevOps build artifacts into local Jenkins artifacts, so other Jenkins jobs can use the latest DevOps artifacts.

This keeps your development and building of tools fully in Azure DevOps. While being still able to use them in a private Jekins behind a VPN connection.

Ofcourse it can also be deployed in other use-cases.

## Usage
Run via the commandline with arguments;

-o, --organization Required.
-t, --pat Required.
-p, --project Required.
-d, --definition Required.
-b, --branch
-a, --artifact Required.
-r, --result (Default: Build.zip)
--help Display this help screen.
--version Display version information.

0 comments on commit ef30335

Please sign in to comment.