Compare commits

...

14 Commits

Author SHA1 Message Date
Julio Barba
b061ec410f Revert: Switch publish agent package task to direct to new pool 2019-12-02 16:54:19 -05:00
Julio Barba
2b63b9c379 Prepare 2.162.0 runner release (#204) 2019-12-02 16:16:35 -05:00
Julio Barba
d93fb70a3e Fix PrepareActions_DownloadActionFromGraph test (#205) 2019-12-02 16:03:42 -05:00
Julio Barba
4ce1bfb58a Switch publish agent package task to direct to new pool 2019-12-02 14:51:46 -05:00
Julio Barba
7a6d9dc5c8 Implement new runner service name convention (#193)
* Limit service name to 80 characters
* Add L0 tests
* New service name convention
* Make RepoOrOrgName a computed property
* Add service name sanitizing logic with L0 test
2019-11-27 14:44:29 -05:00
Julio Barba
de29a39d14 Support downloading/publishing artifacts from Pipelines endpoint (#188)
* Support downloading/publishing artifacts from Pipelines endpoint
* Remove `Path` from everywhere
* Remove unused JobId argument
* PR feedback
* More PR feedback
2019-11-25 13:30:44 -05:00
eric sciple
7d505f7f77 problem matcher default severity (#203) 2019-11-22 13:21:46 -05:00
Thomas Boop
159e4c506a Updated Release Notes to use CLI Downloads (#196)
* Updated Release Notes to use CLI Downloads

* Add Config and Run steps

* Specify Root Drive for Windows

* Remove unactionable steps from readme config
2019-11-19 16:27:36 -05:00
David Kale
45c19eb7cb 150: Support more cpu architectures (#184)
* Cross compile for win-x86, linux-arm, linux-arm64

* Build with actions instead

* Remove win-x86

* Preserve CURRENT_PLATFORM in dev.sh

* build.yaml

* Fix formatting. Remove piplines

* Use 4 space indent consistently

* x32 -> x86

* TEMP: Only test when platform === target runtime

Fix arm64 node externals url

* win-x86 externals

* Temporarily bench rhel

* Add RHEL6, skip L0 on arm for now

* Add stub for downloading new node externals when they are ready

* Remove RHEL6

* Package based on new runtime names

* Remove unused rhel from matrix includes

* Update release, add packages

* RID typo

* Cant cross test arm on x64 hosts

* New arch is a feature

Dont release x86 until we have an e2e test machine

* Fix version

* Get version from file to avoid exec error during package on x64 host for arm package

* Update Release Notes for 2.161.0 (#195)

* More cleanup

* Update release notes
2019-11-13 11:26:06 -05:00
Thomas Boop
9ba971592b V 2.160.2 Release notes and version bump (#190) 2019-11-11 15:45:37 -05:00
Thomas Boop
b27cfb18e6 2.160.1 Runner Release Notes (#171)
* 2.160.1 Runner Release Notes

* Minor verbiage updates to be consistent
2019-11-11 15:45:13 -05:00
Thomas Boop
ced4c2ca50 add-mask is leaking a secret in master if debug or ::echo::on is set (#158)
* Output after processing command to avoid leaking mask

* Remove extra noise output from echo changes

* Omit Echoing of add-mask command

* avoid echoing on debug/warning/error
2019-11-11 15:40:18 -05:00
Thomas Boop
54f21c641f Update dependency docs for OSX and Windows (#162)
* Update Dependency docs for .net 3.0 depedencies

* Update Supported Windows Versions

* Update Supported Mac OS link

* Update docs/start/envosx.md

Fix typo in OSX Version

Co-Authored-By: Lucas Costi <lucascosti@users.noreply.github.com>
2019-11-11 13:43:02 -05:00
Thomas Boop
c5cbac9796 Runner fails to run as a service on windows: Disable Delay Signing on the Runner Service (#185)
* test release

* Reverse test release changes

* Remove Unused Public Keys from Runner Service
2019-11-08 09:49:20 -05:00
58 changed files with 1843 additions and 207 deletions

View File

@@ -13,14 +13,28 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
include:
- os: ubuntu-latest
- runtime: linux-x64
os: ubuntu-latest
devScript: ./dev.sh
- os: macOS-latest
- runtime: linux-arm64
os: ubuntu-latest
devScript: ./dev.sh
- os: windows-latest
- runtime: linux-arm
os: ubuntu-latest
devScript: ./dev.sh
- runtime: osx-x64
os: macOS-latest
devScript: ./dev.sh
- runtime: win-x64
os: windows-latest
devScript: ./dev
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
@@ -28,7 +42,7 @@ jobs:
# Build runner layout
- name: Build & Layout Release
run: |
${{ matrix.devScript }} layout Release
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
working-directory: src
# Run tests
@@ -36,6 +50,7 @@ jobs:
run: |
${{ matrix.devScript }} test
working-directory: src
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
# Create runner package tar.gz/zip
- name: Package Release
@@ -49,5 +64,5 @@ jobs:
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v1
with:
name: runner-package-${{ matrix.os }}
name: runner-package-${{ matrix.runtime }}
path: _package

View File

@@ -16,5 +16,17 @@
"platform": "linux-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz"
},
{
"name": "actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz",
"platform": "linux-arm64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz"
},
{
"name": "actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz",
"platform": "linux-arm",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz"
}
]

View File

@@ -11,6 +11,8 @@ stages:
# Steps template for windows platform
- template: windows.template.yml
parameters:
targetRuntime: win-x64
# Package dotnet core windows dependency (VC++ Redistributable)
- powershell: |
@@ -28,13 +30,13 @@ stages:
displayName: Package UCRT
# Create agent package zip
- script: dev.cmd package Release
- script: dev.cmd package Release win-x64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Windows)
displayName: Publish Artifact (Windows x64)
inputs:
pathToPublish: _package
artifactName: runners
@@ -50,22 +52,76 @@ stages:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: linux-x64
# Create agent package zip
- script: ./dev.sh package Release
- script: ./dev.sh package Release linux-x64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux)
displayName: Publish Artifact (Linux x64)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_osx_agent
- job: build_linux_agent_arm64
################################################################################
displayName: Linux Agent (arm64)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: linux-arm64
# Create agent package zip
- script: ./dev.sh package Release linux-arm64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux ARM64)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_linux_agent_arm
################################################################################
displayName: Linux Agent (arm)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: linux-arm
# Create agent package zip
- script: ./dev.sh package Release linux-arm
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux ARM)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_osx_agent_x64
################################################################################
displayName: macOS Agent (x64)
pool:
@@ -74,15 +130,17 @@ stages:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: osx-x64
# Create agent package zip
- script: ./dev.sh package Release
- script: ./dev.sh package Release osx-x64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (OSX)
displayName: Publish Artifact (OSX x64)
inputs:
pathToPublish: _package
artifactName: runners

View File

@@ -4,7 +4,7 @@
## Supported Versions
- macOS Sierra (10.12) and later versions
- macOS High Sierra (10.13) and later versions
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore2x)
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)

View File

@@ -5,8 +5,8 @@
- Windows 7 64-bit
- Windows 8.1 64-bit
- Windows 10 64-bit
- Windows Server 2008 R2 SP1 64-bit
- Windows Server 2012 R2 64-bit
- Windows Server 2016 64-bit
- Windows Server 2019 64-bit
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)

View File

@@ -1,14 +1,18 @@
parameters:
targetRuntime: ''
steps:
# Build agent layout
- script: ./dev.sh layout Release
- script: ./dev.sh layout Release ${{ parameters.targetRuntime }}
workingDirectory: src
displayName: Build & Layout Release
displayName: Build & Layout Release ${{ parameters.targetRuntime }}
# Run test
- script: ./dev.sh test
workingDirectory: src
displayName: Test
condition: and(ne('${{ parameters.targetRuntime }}', 'linux-arm64'), ne('${{ parameters.targetRuntime }}', 'linux-arm'))
# # Publish test results
# - task: PublishTestResults@2

View File

@@ -1,40 +1,69 @@
## Features
- N/A
- Added the "severity" keyword to allow action authors to set the default severity for problem matchers (#203)
## Bugs
- Reverted removal of additional fields error and warning fields (#147)
- Actions cache would incorrectly cache the action if the tag was updated (#148)
- Fixed generated self-hosted runner names to never go over 80 characters (helps Windows customers) (#193)
- Fixed `PrepareActions_DownloadActionFromGraph` test by pointing to an active Actions repository (#205)
## Misc
- Updated to .NET Core 3.0 (#127)
## Agent Downloads
| | Package |
| ------- | ----------------------------------------------------------------------------------------------------------- |
| Windows x64 | [actions-runner-win-x64-<RUNNER_VERSION>.zip](https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip) |
| macOS | [actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz](https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz) |
| Linux x64 | [actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz](https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz) |
After Download:
- Updated the publish and download artifact actions to use the v2 endpoint (#188)
- Updated the service name on self-hosted runner name to include repository or organization information (#193)
## Windows x64
``` bash
C:\> mkdir myagent && cd myagent
C:\myagent> Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
We recommend configuring the runner under "<DRIVE>:\actions-runner". This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows
```
// Create a folder under the drive root
mkdir \actions-runner ; cd \actions-runner
// Download the latest runner package
Invoke-WebRequest -Uri https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip -OutFile actions-runner-win-x64-<RUNNER_VERSION>.zip
// Extract the installer
Add-Type -AssemblyName System.IO.Compression.FileSystem ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
```
## OSX
``` bash
~/$ mkdir myagent && cd myagent
~/myagent$ tar xzf ~/Downloads/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
```
## Linux x64
``` bash
~/$ mkdir myagent && cd myagent
~/myagent$ tar xzf ~/Downloads/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
```
## Linux arm64 (Pre-release)
``` bash
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
```
## Linux arm (Pre-release)
``` bash
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
```
## Using your self hosted runner
For additional details about configuring, running, or shutting down the runner please check out our [product docs.](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners)

View File

@@ -16,9 +16,6 @@
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux'">
<DefineConstants>$(DefineConstants);OS_LINUX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>$(DefineConstants);OS_RHEL6</DefineConstants>
</PropertyGroup>
<!-- Set Platform/bitness vars -->
<PropertyGroup Condition="'$(BUILD_OS)' == 'Windows' AND ('$(PackageRuntime)' == 'win-x64' OR '$(PackageRuntime)' == '')">
@@ -35,12 +32,12 @@
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND ('$(PackageRuntime)' == 'linux-x64' OR '$(PackageRuntime)' == '')">
<DefineConstants>$(DefineConstants);X64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>$(DefineConstants);X64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>$(DefineConstants);ARM</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND '$(PackageRuntime)' == 'linux-arm64'">
<DefineConstants>$(DefineConstants);ARM64</DefineConstants>
</PropertyGroup>
<!-- Set TRACE/DEBUG vars -->
<PropertyGroup>

View File

@@ -123,9 +123,9 @@ function acquireExternalTool() {
}
# Download the external tools only for Windows.
if [[ "$PACKAGERUNTIME" == "win-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/win-x64/node.exe" node12/bin
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/win-x64/node.lib" node12/bin
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/$PACKAGERUNTIME/node.exe" node12/bin
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/$PACKAGERUNTIME/node.lib" node12/bin
if [[ "$PRECACHE" != "" ]]; then
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
fi
@@ -136,13 +136,23 @@ if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-darwin-x64.tar.gz" node12 fix_nested_dir
fi
# Download the external tools common across Linux PACKAGERUNTIMEs (excluding OSX).
if [[ "$PACKAGERUNTIME" == "linux-x64" || "$PACKAGERUNTIME" == "rhel.6-x64" ]]; then
# Download the external tools for Linux PACKAGERUNTIMEs.
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-x64.tar.gz" node12 fix_nested_dir
# TODO: Repath this blob to use a consistent version format (_ vs .)
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/12_4_0/alpine/node-v${NODE12_VERSION}-alpine.tar.gz" node12_alpine
# acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/12.13.0/alpine/x64/node-v${NODE12_VERSION}-alpine-x64.tar.gz" node12_alpine
fi
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-arm64.tar.gz" node12 fix_nested_dir
# TODO: alpine node runtime for arm64(8)
# acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/12.13.0/alpine/arm64/node-v${NODE12_VERSION}-alpine-arm64.tar.gz" node12_alpine
fi
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-armv7l.tar.gz" node12 fix_nested_dir
# TODO: alpine node runtime for arm32(7)
# Need to set up custom gcc toolchain to cross compile on x64 ubuntu for armv7 (per https://github.com/nodejs/node/blob/master/BUILDING.md)
# acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/12.13.0/alpine/arm/node-v${NODE12_VERSION}-alpine-arm.tar.gz" node12_alpine
fi

View File

@@ -1,6 +1,8 @@
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
@@ -51,6 +53,34 @@ namespace GitHub.Runner.Common
[DataMember(EmitDefaultValue = false)]
public string MonitorSocketAddress { get; set; }
/// <summary>
// Computed property for convenience. Can either return:
// 1. If runner was configured at the repo level, returns something like: "myorg/myrepo"
// 2. If runner was configured at the org level, returns something like: "myorg"
/// </summary>
public string RepoOrOrgName
{
get
{
Uri accountUri = new Uri(this.ServerUrl);
string repoOrOrgName = string.Empty;
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase))
{
Uri gitHubUrl = new Uri(this.GitHubUrl);
// Use the "NWO part" from the GitHub URL path
repoOrOrgName = gitHubUrl.AbsolutePath.Trim('/');
}
else
{
repoOrOrgName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
}
return repoOrOrgName;
}
}
}
[DataContract]

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -54,6 +54,8 @@ namespace GitHub.Runner.Common.Util
return "X64";
case Constants.Architecture.Arm:
return "ARM";
case Constants.Architecture.Arm64:
return "ARM64";
default:
throw new NotSupportedException(); // Should never reach here.
}

View File

@@ -12,8 +12,8 @@ namespace GitHub.Runner.Listener.Configuration
public class OsxServiceControlManager : ServiceControlManager, ILinuxServiceControlManager
{
// This is the name you would see when you do `systemctl list-units | grep runner`
private const string _svcNamePattern = "actions.runner.{0}.{1}.{2}";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1}.{2})";
private const string _svcNamePattern = "actions.runner.{0}.{1}";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1})";
private const string _shTemplate = "darwin.svc.sh.template";
private const string _svcShName = "svc.sh";

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
@@ -37,25 +38,38 @@ namespace GitHub.Runner.Listener.Configuration
serviceName = string.Empty;
serviceDisplayName = string.Empty;
Uri accountUri = new Uri(settings.ServerUrl);
string accountName = string.Empty;
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(settings.RepoOrOrgName))
{
accountName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
}
else
{
accountName = accountUri.Host.Split('.').FirstOrDefault();
throw new InvalidOperationException($"Cannot find GitHub repository/organization name from server url: '{settings.ServerUrl}'");
}
if (string.IsNullOrEmpty(accountName))
// For the service name, replace any characters outside of the alpha-numeric set and ".", "_", "-" with "-"
Regex regex = new Regex(@"[^0-9a-zA-Z._\-]");
string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-");
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName);
if (serviceName.Length > 80)
{
throw new InvalidOperationException($"Cannot find GitHub organization name from server url: '{settings.ServerUrl}'");
Trace.Verbose($"Calculated service name is too long (> 80 chars). Trying again by calculating a shorter name.");
int exceededCharLength = serviceName.Length - 80;
string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, 45);
exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length;
string runnerNameSubstring = settings.AgentName;
// Only trim runner name if it's really necessary
if (exceededCharLength > 0)
{
runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength);
}
serviceName = StringUtil.Format(serviceNamePattern, accountName, settings.PoolName, settings.AgentName);
serviceDisplayName = StringUtil.Format(serviceDisplayNamePattern, accountName, settings.PoolName, settings.AgentName);
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring);
}
serviceDisplayName = StringUtil.Format(serviceDisplayNamePattern, repoOrOrgName, settings.AgentName);
Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration.");
}

View File

@@ -13,8 +13,8 @@ namespace GitHub.Runner.Listener.Configuration
public class SystemDControlManager : ServiceControlManager, ILinuxServiceControlManager
{
// This is the name you would see when you do `systemctl list-units | grep runner`
private const string _svcNamePattern = "actions.runner.{0}.{1}.{2}.service";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1}.{2})";
private const string _svcNamePattern = "actions.runner.{0}.{1}.service";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1})";
private const string _shTemplate = "systemd.svc.sh.template";
private const string _shName = "svc.sh";

View File

@@ -15,8 +15,8 @@ namespace GitHub.Runner.Listener.Configuration
{
public const string WindowsServiceControllerName = "RunnerService.exe";
private const string ServiceNamePattern = "actionsrunner.{0}.{1}.{2}";
private const string ServiceDisplayNamePattern = "GitHub Actions Runner ({0}.{1}.{2})";
private const string ServiceNamePattern = "actions.runner.{0}.{1}";
private const string ServiceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
private INativeWindowsServiceHelper _windowsServiceHelper;
private ITerminal _term;

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -50,28 +50,61 @@ namespace GitHub.Runner.Plugins.Artifact
throw new ArgumentException($"Run Id is not an Int32: {buildIdStr}");
}
context.Output($"Download artifact '{artifactName}' to: '{targetPath}'");
// Determine whether to call Pipelines or Build endpoint to publish artifact based on variable setting
string usePipelinesArtifactEndpointVar = context.Variables.GetValueOrDefault("Runner.UseActionsArtifactsApis")?.Value;
bool.TryParse(usePipelinesArtifactEndpointVar, out bool usePipelinesArtifactEndpoint);
string containerPath;
long containerId;
context.Output($"Downloading artifact '{artifactName}' to: '{targetPath}'");
if (usePipelinesArtifactEndpoint)
{
context.Debug("Downloading artifact using v2 endpoint");
// Definition ID is a dummy value only used by HTTP client routing purposes
int definitionId = 1;
var pipelinesHelper = new PipelinesServer(context.VssConnection);
var actionsStorageArtifact = await pipelinesHelper.GetActionsStorageArtifact(definitionId, buildId, artifactName, token);
if (actionsStorageArtifact == null)
{
throw new Exception($"The actions storage artifact for '{artifactName}' could not be found, or is no longer available");
}
containerPath = actionsStorageArtifact.Name; // In actions storage artifacts, name equals the path
containerId = actionsStorageArtifact.ContainerId;
}
else
{
context.Debug("Downloading artifact using v1 endpoint");
BuildServer buildHelper = new BuildServer(context.VssConnection);
BuildArtifact buildArtifact = await buildHelper.GetArtifact(projectId, buildId, artifactName, token);
if (string.Equals(buildArtifact.Resource.Type, "Container", StringComparison.OrdinalIgnoreCase))
if (string.Equals(buildArtifact.Resource.Type, "Container", StringComparison.OrdinalIgnoreCase) ||
// Artifact was published by Pipelines endpoint, check new type here to handle rollback scenario
string.Equals(buildArtifact.Resource.Type, "Actions_Storage", StringComparison.OrdinalIgnoreCase))
{
string containerUrl = buildArtifact.Resource.Data;
string[] parts = containerUrl.Split(new[] { '/' }, 3);
if (parts.Length < 3 || !long.TryParse(parts[1], out long containerId))
if (parts.Length < 3 || !long.TryParse(parts[1], out containerId))
{
throw new ArgumentOutOfRangeException($"Invalid container url '{containerUrl}' for artifact '{buildArtifact.Name}'");
}
string containerPath = parts[2];
FileContainerServer fileContainerServer = new FileContainerServer(context.VssConnection, projectId, containerId, containerPath);
await fileContainerServer.DownloadFromContainerAsync(context, targetPath, token);
containerPath = parts[2];
}
else
{
throw new NotSupportedException($"Invalid artifact type: {buildArtifact.Resource.Type}");
}
}
FileContainerServer fileContainerServer = new FileContainerServer(context.VssConnection, projectId, containerId, containerPath);
await fileContainerServer.DownloadFromContainerAsync(context, targetPath, token);
context.Output("Artifact download finished.");
}

View File

@@ -0,0 +1,60 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Actions.Pipelines.WebApi;
using GitHub.Services.WebApi;
using GitHub.Runner.Sdk;
using Pipelines = GitHub.Actions.Pipelines.WebApi;
namespace GitHub.Runner.Plugins.Artifact
{
// A client wrapper interacting with Pipelines's Artifact API
public class PipelinesServer
{
private readonly PipelinesHttpClient _pipelinesHttpClient;
public PipelinesServer(VssConnection connection)
{
ArgUtil.NotNull(connection, nameof(connection));
_pipelinesHttpClient = connection.GetClient<PipelinesHttpClient>();
}
// Associate the specified Actions Storage artifact with a pipeline
public async Task<Pipelines.ActionsStorageArtifact> AssociateActionsStorageArtifactAsync(
int pipelineId,
int runId,
long containerId,
string name,
long size,
CancellationToken cancellationToken = default(CancellationToken))
{
CreateArtifactParameters parameters = new CreateActionsStorageArtifactParameters()
{
Name = name,
ContainerId = containerId,
Size = size
};
return await _pipelinesHttpClient.CreateArtifactAsync(
parameters,
pipelineId,
runId,
cancellationToken: cancellationToken) as Pipelines.ActionsStorageArtifact;
}
// Get named Actions Storage artifact for a pipeline
public async Task<Pipelines.ActionsStorageArtifact> GetActionsStorageArtifact(
int pipelineId,
int runId,
string name,
CancellationToken cancellationToken)
{
return await _pipelinesHttpClient.GetArtifactAsync(
pipelineId,
runId,
name,
cancellationToken: cancellationToken) as Pipelines.ActionsStorageArtifact;
}
}
}

View File

@@ -68,27 +68,59 @@ namespace GitHub.Runner.Plugins.Artifact
string containerIdStr = context.Variables.GetValueOrDefault(BuildVariables.ContainerId)?.Value ?? string.Empty;
if (!long.TryParse(containerIdStr, out long containerId))
{
throw new ArgumentException($"Container Id is not a Int64: {containerIdStr}");
throw new ArgumentException($"Container Id is not an Int64: {containerIdStr}");
}
context.Output($"Uploading artifact '{artifactName}' from '{fullPath}' for run #{buildId}");
FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId, containerId, artifactName);
var propertiesDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
long size = 0;
try
{
long size = await fileContainerHelper.CopyToContainerAsync(context, fullPath, token);
size = await fileContainerHelper.CopyToContainerAsync(context, fullPath, token);
propertiesDictionary.Add("artifactsize", size.ToString());
context.Output($"Uploaded '{size}' bytes from '{fullPath}' to server");
}
// if any of the results were successful, make sure to attach them to the build
finally
{
// Determine whether to call Pipelines or Build endpoint to publish artifact based on variable setting
string usePipelinesArtifactEndpointVar = context.Variables.GetValueOrDefault("Runner.UseActionsArtifactsApis")?.Value;
bool.TryParse(usePipelinesArtifactEndpointVar, out bool usePipelinesArtifactEndpoint);
if (usePipelinesArtifactEndpoint)
{
// Definition ID is a dummy value only used by HTTP client routing purposes
int definitionId = 1;
PipelinesServer pipelinesHelper = new PipelinesServer(context.VssConnection);
var artifact = await pipelinesHelper.AssociateActionsStorageArtifactAsync(
definitionId,
buildId,
containerId,
artifactName,
size,
token);
context.Output($"Associated artifact {artifactName} ({artifact.ContainerId}) with run #{buildId}");
context.Debug($"Associated artifact using v2 endpoint");
}
else
{
string fileContainerFullPath = StringUtil.Format($"#/{containerId}/{artifactName}");
BuildServer buildHelper = new BuildServer(context.VssConnection);
string jobId = context.Variables.GetValueOrDefault(WellKnownDistributedTaskVariables.JobId).Value ?? string.Empty;
var artifact = await buildHelper.AssociateArtifact(projectId, buildId, jobId, artifactName, ArtifactResourceTypes.Container, fileContainerFullPath, propertiesDictionary, token);
context.Output($"Associated artifact {artifactName} ({artifact.Id}) with run #{buildId}");
context.Debug($"Associated artifact using v1 endpoint");
}
}
}
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -122,5 +122,10 @@ namespace GitHub.Runner.Sdk
return format;
}
}
public static string SubstringPrefix(string value, int count)
{
return value?.Substring(0, Math.Min(value.Length, count));
}
}
}

View File

@@ -9,9 +9,8 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RunnerService</RootNamespace>
<AssemblyName>RunnerService</AssemblyName>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>FinalPublicKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>true</DelaySign>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -64,7 +63,6 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="FinalPublicKey.snk" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource.resx">

View File

@@ -73,7 +73,7 @@ namespace GitHub.Runner.Worker
return false;
}
// process action command in serialize oreder.
// process action command in serialize order.
lock (_commandSerializeLock)
{
if (_stopProcessCommand)
@@ -107,32 +107,19 @@ namespace GitHub.Runner.Worker
}
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
{
bool commandHasBeenOutput = false;
if (context.EchoOnActionCommand && !extension.OmitEcho)
{
context.Output(input);
}
try
{
if (context.EchoOnActionCommand)
{
context.Output(input);
context.Debug($"Processing command '{actionCommand.Command}'");
commandHasBeenOutput = true;
}
extension.ProcessCommand(context, input, actionCommand);
if (context.EchoOnActionCommand)
{
context.Debug($"Processed command '{actionCommand.Command}' successfully");
}
}
catch (Exception ex)
{
if (!commandHasBeenOutput)
{
context.Output(input);
}
context.Error($"Unable to process command '{input}' successfully.");
var commandInformation = extension.OmitEcho ? extension.Command : input;
context.Error($"Unable to process command '{commandInformation}' successfully.");
context.Error(ex);
context.CommandResult = TaskResult.Failed;
}
@@ -151,6 +138,7 @@ namespace GitHub.Runner.Worker
public interface IActionCommandExtension : IExtension
{
string Command { get; }
bool OmitEcho { get; }
void ProcessCommand(IExecutionContext context, string line, ActionCommand command);
}
@@ -158,6 +146,7 @@ namespace GitHub.Runner.Worker
public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "internal-set-repo-path";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -187,6 +176,7 @@ namespace GitHub.Runner.Worker
public sealed class SetEnvCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "set-env";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -211,6 +201,7 @@ namespace GitHub.Runner.Worker
public sealed class SetOutputCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "set-output";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -234,6 +225,7 @@ namespace GitHub.Runner.Worker
public sealed class SaveStateCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "save-state";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -257,6 +249,7 @@ namespace GitHub.Runner.Worker
public sealed class AddMaskCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "add-mask";
public bool OmitEcho => true;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -268,6 +261,11 @@ namespace GitHub.Runner.Worker
}
else
{
if (context.EchoOnActionCommand)
{
context.Output($"::{Command}::***");
}
HostContext.SecretMasker.AddValue(command.Data);
Trace.Info($"Add new secret mask with length of {command.Data.Length}");
}
@@ -277,6 +275,7 @@ namespace GitHub.Runner.Worker
public sealed class AddPathCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "add-path";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -291,6 +290,7 @@ namespace GitHub.Runner.Worker
public sealed class AddMatcherCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "add-matcher";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -337,6 +337,7 @@ namespace GitHub.Runner.Worker
public sealed class RemoveMatcherCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "remove-matcher";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -404,6 +405,7 @@ namespace GitHub.Runner.Worker
public sealed class DebugCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "debug";
public bool OmitEcho => true;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -431,6 +433,7 @@ namespace GitHub.Runner.Worker
{
public abstract IssueType Type { get; }
public abstract string Command { get; }
public bool OmitEcho => true;
public Type ExtensionType => typeof(IActionCommandExtension);
@@ -510,6 +513,8 @@ namespace GitHub.Runner.Worker
public abstract class GroupingCommandExtension : RunnerService, IActionCommandExtension
{
public abstract string Command { get; }
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
@@ -522,6 +527,7 @@ namespace GitHub.Runner.Worker
public sealed class EchoCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "echo";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);

View File

@@ -57,9 +57,6 @@ namespace GitHub.Runner.Worker
{
throw new NotSupportedException("Container feature is not supported when runner is already running inside container.");
}
#elif OS_RHEL6
// Red Hat and CentOS 6 do not support the container feature
throw new NotSupportedException("Runner does not support the container feature on Red Hat Enterprise Linux 6 or CentOS 6.");
#else
var initProcessCgroup = File.ReadLines("/proc/1/cgroup");
if (initProcessCgroup.Any(x => x.IndexOf(":/docker/", StringComparison.OrdinalIgnoreCase) >= 0))

View File

@@ -20,6 +20,7 @@ namespace GitHub.Runner.Worker
public sealed class IssueMatcher
{
private string _defaultSeverity;
private string _owner;
private IssuePattern[] _patterns;
private IssueMatch[] _state;
@@ -27,6 +28,7 @@ namespace GitHub.Runner.Worker
public IssueMatcher(IssueMatcherConfig config, TimeSpan timeout)
{
_owner = config.Owner;
_defaultSeverity = config.Severity;
_patterns = config.Patterns.Select(x => new IssuePattern(x , timeout)).ToArray();
Reset();
}
@@ -37,13 +39,26 @@ namespace GitHub.Runner.Worker
{
if (_owner == null)
{
_owner = String.Empty;
_owner = string.Empty;
}
return _owner;
}
}
public string DefaultSeverity
{
get
{
if (_defaultSeverity == null)
{
_defaultSeverity = string.Empty;
}
return _defaultSeverity;
}
}
public IssueMatch Match(string line)
{
// Single pattern
@@ -54,7 +69,7 @@ namespace GitHub.Runner.Worker
if (regexMatch.Success)
{
return new IssueMatch(null, pattern, regexMatch.Groups);
return new IssueMatch(null, pattern, regexMatch.Groups, DefaultSeverity);
}
return null;
@@ -95,7 +110,7 @@ namespace GitHub.Runner.Worker
}
// Return
return new IssueMatch(runningMatch, pattern, regexMatch.Groups);
return new IssueMatch(runningMatch, pattern, regexMatch.Groups, DefaultSeverity);
}
// Not the last pattern
else
@@ -169,7 +184,7 @@ namespace GitHub.Runner.Worker
public sealed class IssueMatch
{
public IssueMatch(IssueMatch runningMatch, IssuePattern pattern, GroupCollection groups)
public IssueMatch(IssueMatch runningMatch, IssuePattern pattern, GroupCollection groups, string defaultSeverity = null)
{
File = runningMatch?.File ?? GetValue(groups, pattern.File);
Line = runningMatch?.Line ?? GetValue(groups, pattern.Line);
@@ -178,6 +193,11 @@ namespace GitHub.Runner.Worker
Code = runningMatch?.Code ?? GetValue(groups, pattern.Code);
Message = runningMatch?.Message ?? GetValue(groups, pattern.Message);
FromPath = runningMatch?.FromPath ?? GetValue(groups, pattern.FromPath);
if (string.IsNullOrEmpty(Severity) && !string.IsNullOrEmpty(defaultSeverity))
{
Severity = defaultSeverity;
}
}
public string File { get; }
@@ -256,6 +276,9 @@ namespace GitHub.Runner.Worker
[DataMember(Name = "owner")]
private string _owner;
[DataMember(Name = "severity")]
private string _severity;
[DataMember(Name = "pattern")]
private IssuePatternConfig[] _patterns;
@@ -265,7 +288,7 @@ namespace GitHub.Runner.Worker
{
if (_owner == null)
{
_owner = String.Empty;
_owner = string.Empty;
}
return _owner;
@@ -277,6 +300,24 @@ namespace GitHub.Runner.Worker
}
}
public string Severity
{
get
{
if (_severity == null)
{
_severity = string.Empty;
}
return _severity;
}
set
{
_severity = value;
}
}
public IssuePatternConfig[] Patterns
{
get
@@ -303,6 +344,17 @@ namespace GitHub.Runner.Worker
throw new ArgumentException("Owner must not be empty");
}
// Validate severity
switch ((_severity ?? string.Empty).ToUpperInvariant())
{
case "":
case "ERROR":
case "WARNING":
break;
default:
throw new ArgumentException($"Matcher '{_owner}' contains unexpected default severity '{_severity}'");
}
// Validate at least one pattern
if (_patterns == null || _patterns.Length == 0)
{

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -0,0 +1,44 @@
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.Actions.Pipelines.WebApi
{
[DataContract]
public class ActionsStorageArtifact : Artifact
{
public ActionsStorageArtifact()
: base(ArtifactType.Actions_Storage)
{
}
/// <summary>
/// File Container ID
/// </summary>
[DataMember]
public long ContainerId
{
get;
set;
}
/// <summary>
/// Size of the file in bytes
/// </summary>
[DataMember]
public long Size
{
get;
set;
}
/// <summary>
/// Signed content url for downloading the artifact
/// </summary>
[DataMember]
public SignedUrl SignedContent
{
get;
set;
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Runtime.Serialization;
using GitHub.Actions.Pipelines.WebApi.Contracts;
using Newtonsoft.Json;
namespace GitHub.Actions.Pipelines.WebApi
{
[DataContract]
[KnownType(typeof(ActionsStorageArtifact))]
[JsonConverter(typeof(ArtifactJsonConverter))]
public class Artifact
{
public Artifact(ArtifactType type)
{
Type = type;
}
/// <summary>
/// The type of the artifact.
/// </summary>
[DataMember]
public ArtifactType Type
{
get;
}
/// <summary>
/// The name of the artifact.
/// </summary>
[DataMember]
public string Name
{
get;
set;
}
/// <summary>
/// Self-referential url
/// </summary>
[DataMember]
public string Url
{
get;
set;
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Reflection;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace GitHub.Actions.Pipelines.WebApi
{
public abstract class ArtifactBaseJsonConverter<T> : VssSecureJsonConverter where T : class
{
public override bool CanConvert(Type objectType)
{
return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
// by returning false, the converter doesn't take part in writes
// which means we use the default serialization logic
public override bool CanWrite
{
get
{
return false;
}
}
protected abstract T Create(Type objectType);
protected abstract T Create(ArtifactType type);
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartObject)
{
return null;
}
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
{
return existingValue;
}
// if objectType is one of our known types, we can ignore the type property
T targetObject = Create(objectType);
// read the data into a JObject so we can look at it
var value = JObject.Load(reader);
if (targetObject == null)
{
// use the Type property
var typeProperty = contract.Properties.GetClosestMatchProperty("Type");
if (typeProperty == null)
{
// we don't know the type. just bail
return existingValue;
}
if (!value.TryGetValue(typeProperty.PropertyName, StringComparison.OrdinalIgnoreCase, out var typeValue))
{
// a type property exists on the contract, but the JObject has no value for it
return existingValue;
}
var type = UnknownEnum.Parse<ArtifactType>(typeValue.ToString());
targetObject = Create(type);
}
if (targetObject != null)
{
using (var objectReader = value.CreateReader())
{
serializer.Populate(objectReader, targetObject);
}
}
return targetObject;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
namespace GitHub.Actions.Pipelines.WebApi.Contracts
{
public class ArtifactJsonConverter : ArtifactBaseJsonConverter<Artifact>
{
protected override Artifact Create(Type objectType)
{
if (objectType == typeof(ActionsStorageArtifact))
{
return new ActionsStorageArtifact();
}
else
{
return null;
}
}
protected override Artifact Create(ArtifactType type)
{
if (type == ArtifactType.Actions_Storage)
{
return new ActionsStorageArtifact();
}
return null;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace GitHub.Actions.Pipelines.WebApi
{
[DataContract]
[JsonConverter(typeof(ArtifactTypeEnumJsonConverter))]
public enum ArtifactType
{
Unknown = 0,
Actions_Storage = 1
}
}

View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Net.Http.Formatting;
namespace GitHub.Actions.Pipelines.WebApi
{
public class ArtifactTypeEnumJsonConverter : UnknownEnumJsonConverter
{
//json.net v12 exposes a "NamingStrategy" member that can do all this. We are at json.net v10 which only supports camel case.
//This is a poor man's way to fake it
public override void WriteJson(JsonWriter writer, object enumValue, JsonSerializer serializer)
{
var value = (ArtifactType)enumValue;
if (value == ArtifactType.Actions_Storage)
{
writer.WriteValue("actions_storage");
}
else
{
base.WriteJson(writer, enumValue, serializer);
}
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Runtime.Serialization;
namespace GitHub.Actions.Pipelines.WebApi
{
[DataContract]
public class CreateActionsStorageArtifactParameters : CreateArtifactParameters
{
public CreateActionsStorageArtifactParameters()
: base(ArtifactType.Actions_Storage)
{
}
/// <summary>
/// the id of the file container
/// </summary>
[DataMember]
public long ContainerId
{
get;
set;
}
/// <summary>
/// Size of the file in bytes
/// </summary>
[DataMember]
public long Size
{
get;
set;
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace GitHub.Actions.Pipelines.WebApi
{
[DataContract]
[KnownType(typeof(CreateActionsStorageArtifactParameters))]
[JsonConverter(typeof(CreateArtifactParametersJsonConverter))]
public class CreateArtifactParameters
{
protected CreateArtifactParameters(ArtifactType type)
{
Type = type;
}
/// <summary>
/// The type of the artifact.
/// </summary>
[DataMember]
public ArtifactType Type
{
get;
}
/// <summary>
/// The name of the artifact.
/// </summary>
[DataMember]
public string Name
{
get;
set;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
namespace GitHub.Actions.Pipelines.WebApi
{
public class CreateArtifactParametersJsonConverter : ArtifactBaseJsonConverter<CreateArtifactParameters>
{
protected override CreateArtifactParameters Create(Type objectType)
{
if (objectType == typeof(CreateActionsStorageArtifactParameters))
{
return new CreateActionsStorageArtifactParameters();
}
else
{
return null;
}
}
protected override CreateArtifactParameters Create(ArtifactType type)
{
if (type == ArtifactType.Actions_Storage)
{
return new CreateActionsStorageArtifactParameters();
}
return null;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
namespace GitHub.Actions.Pipelines.WebApi
{
/// <summary>
/// $expand options for GetArtifact and ListArtifacts.
/// </summary>
[TypeConverter(typeof(KnownFlagsEnumTypeConverter))]
[Flags]
public enum GetArtifactExpandOptions
{
None = 0,
SignedContent = 1,
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using GitHub.Services.Common;
namespace GitHub.Actions.Pipelines.WebApi
{
public static class FlagsEnum
{
public static TEnum ParseKnownFlags<TEnum>(string stringValue) where TEnum : System.Enum
{
return (TEnum)ParseKnownFlags(typeof(TEnum), stringValue);
}
/// <summary>
/// Parse known enum flags in a comma-separated string. Unknown flags are ignored. Allows for degraded compatibility without serializing enums to integers.
/// </summary>
/// <remarks>
/// Case insensitive. Both standard and EnumMemberAttribute names are parsed.
/// </remarks>
/// <exception cref="NullReferenceException">Thrown if stringValue is null.</exception>
/// <exception cref="ArgumentException">Thrown if a flag name is empty.</exception>
public static object ParseKnownFlags(Type enumType, string stringValue)
{
ArgumentUtility.CheckForNull(enumType, nameof(enumType));
if (!enumType.IsEnum)
{
throw new ArgumentException(PipelinesWebApiResources.FlagEnumTypeRequired());
}
// Check for the flags attribute in debug. Skip this reflection in release.
Debug.Assert(enumType.GetCustomAttributes(typeof(FlagsAttribute), inherit: false).Any(), "FlagsEnum only intended for enums with the Flags attribute.");
// The exception types below are based on Enum.TryParseEnum (http://index/?query=TryParseEnum&rightProject=mscorlib&file=system%5Cenum.cs&rightSymbol=bhaeh2vnegwo)
if (stringValue == null)
{
throw new ArgumentNullException(stringValue);
}
if (String.IsNullOrWhiteSpace(stringValue))
{
throw new ArgumentException(PipelinesWebApiResources.NonEmptyEnumElementsRequired(stringValue));
}
if (UInt64.TryParse(stringValue, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out ulong ulongValue))
{
return Enum.Parse(enumType, stringValue);
}
var enumNames = Enum.GetNames(enumType).ToHashSet(name => name, StringComparer.OrdinalIgnoreCase);
var enumMemberMappings = new Lazy<IDictionary<string, string>>(() =>
{
IDictionary<string, string> mappings = null;
foreach (var field in enumType.GetFields())
{
if (field.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault() is EnumMemberAttribute enumMemberAttribute)
{
if (mappings == null)
{
mappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
mappings.Add(enumMemberAttribute.Value, field.GetValue(null).ToString());
}
}
return mappings;
});
var values = stringValue.Split(s_enumSeparatorCharArray);
var matches = new List<string>();
for (int i = 0; i < values.Length; i++)
{
string value = values[i].Trim();
if (String.IsNullOrEmpty(value))
{
throw new ArgumentException(PipelinesWebApiResources.NonEmptyEnumElementsRequired(stringValue));
}
if (enumNames.Contains(value))
{
matches.Add(value);
}
else if (enumMemberMappings.Value != null && enumMemberMappings.Value.TryGetValue(value, out string matchingValue))
{
matches.Add(matchingValue);
}
}
if (!matches.Any())
{
return Enum.Parse(enumType, "0");
}
string matchesString = String.Join(", ", matches);
return Enum.Parse(enumType, matchesString, ignoreCase: true);
}
private static readonly char[] s_enumSeparatorCharArray = new char[] { ',' };
}
}

View File

@@ -0,0 +1,273 @@
/*
* ---------------------------------------------------------
* Copyright(C) Microsoft Corporation. All rights reserved.
* ---------------------------------------------------------
*
* ---------------------------------------------------------
* Generated file, DO NOT EDIT
* ---------------------------------------------------------
*
* See following wiki page for instructions on how to regenerate:
* https://aka.ms/azure-devops-client-generation
*
* Configuration file:
* actions\client\webapi\clientgeneratorconfigs\pipelines.genclient.json
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Actions.Pipelines.WebApi
{
[ResourceArea(PipelinesArea.IdString)]
public abstract class PipelinesHttpClientBase : VssHttpClientBase
{
public PipelinesHttpClientBase(Uri baseUrl, VssCredentials credentials)
: base(baseUrl, credentials)
{
}
public PipelinesHttpClientBase(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings)
: base(baseUrl, credentials, settings)
{
}
public PipelinesHttpClientBase(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, handlers)
{
}
public PipelinesHttpClientBase(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, settings, handlers)
{
}
public PipelinesHttpClientBase(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler)
: base(baseUrl, pipeline, disposeHandler)
{
}
/// <summary>
/// [Preview API] Associates an artifact with a run.
/// </summary>
/// <param name="createArtifactParameters"></param>
/// <param name="pipelineId">The ID of the pipeline.</param>
/// <param name="runId">The ID of the run.</param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<Artifact> CreateArtifactAsync(
CreateArtifactParameters createArtifactParameters,
int pipelineId,
int runId,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("85023071-bd5e-4438-89b0-2a5bf362a19d");
object routeValues = new { pipelineId = pipelineId, runId = runId };
HttpContent content = new ObjectContent<CreateArtifactParameters>(createArtifactParameters, new VssJsonMediaTypeFormatter(true));
return SendAsync<Artifact>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken,
content: content);
}
/// <summary>
/// [Preview API] Associates an artifact with a run.
/// </summary>
/// <param name="createArtifactParameters"></param>
/// <param name="project">Project ID or project name</param>
/// <param name="pipelineId">The ID of the pipeline.</param>
/// <param name="runId">The ID of the run.</param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<Artifact> CreateArtifactAsync(
CreateArtifactParameters createArtifactParameters,
string project,
int pipelineId,
int runId,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("85023071-bd5e-4438-89b0-2a5bf362a19d");
object routeValues = new { project = project, pipelineId = pipelineId, runId = runId };
HttpContent content = new ObjectContent<CreateArtifactParameters>(createArtifactParameters, new VssJsonMediaTypeFormatter(true));
return SendAsync<Artifact>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken,
content: content);
}
/// <summary>
/// [Preview API] Associates an artifact with a run.
/// </summary>
/// <param name="createArtifactParameters"></param>
/// <param name="project">Project ID</param>
/// <param name="pipelineId">The ID of the pipeline.</param>
/// <param name="runId">The ID of the run.</param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<Artifact> CreateArtifactAsync(
CreateArtifactParameters createArtifactParameters,
Guid project,
int pipelineId,
int runId,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("85023071-bd5e-4438-89b0-2a5bf362a19d");
object routeValues = new { project = project, pipelineId = pipelineId, runId = runId };
HttpContent content = new ObjectContent<CreateArtifactParameters>(createArtifactParameters, new VssJsonMediaTypeFormatter(true));
return SendAsync<Artifact>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken,
content: content);
}
/// <summary>
/// [Preview API] Get a specific artifact
/// </summary>
/// <param name="project">Project ID or project name</param>
/// <param name="pipelineId"></param>
/// <param name="runId"></param>
/// <param name="artifactName"></param>
/// <param name="expand"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<Artifact> GetArtifactAsync(
string project,
int pipelineId,
int runId,
string artifactName,
GetArtifactExpandOptions? expand = null,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("GET");
Guid locationId = new Guid("85023071-bd5e-4438-89b0-2a5bf362a19d");
object routeValues = new { project = project, pipelineId = pipelineId, runId = runId };
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("artifactName", artifactName);
if (expand != null)
{
queryParams.Add("$expand", expand.Value.ToString());
}
return SendAsync<Artifact>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
queryParameters: queryParams,
userState: userState,
cancellationToken: cancellationToken);
}
/// <summary>
/// [Preview API] Get a specific artifact
/// </summary>
/// <param name="project">Project ID</param>
/// <param name="pipelineId"></param>
/// <param name="runId"></param>
/// <param name="artifactName"></param>
/// <param name="expand"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<Artifact> GetArtifactAsync(
Guid project,
int pipelineId,
int runId,
string artifactName,
GetArtifactExpandOptions? expand = null,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("GET");
Guid locationId = new Guid("85023071-bd5e-4438-89b0-2a5bf362a19d");
object routeValues = new { project = project, pipelineId = pipelineId, runId = runId };
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("artifactName", artifactName);
if (expand != null)
{
queryParams.Add("$expand", expand.Value.ToString());
}
return SendAsync<Artifact>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
queryParameters: queryParams,
userState: userState,
cancellationToken: cancellationToken);
}
/// <summary>
/// [Preview API] Get a specific artifact
/// </summary>
/// <param name="pipelineId"></param>
/// <param name="runId"></param>
/// <param name="artifactName"></param>
/// <param name="expand"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<Artifact> GetArtifactAsync(
int pipelineId,
int runId,
string artifactName,
GetArtifactExpandOptions? expand = null,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("GET");
Guid locationId = new Guid("85023071-bd5e-4438-89b0-2a5bf362a19d");
object routeValues = new { pipelineId = pipelineId, runId = runId };
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("artifactName", artifactName);
if (expand != null)
{
queryParams.Add("$expand", expand.Value.ToString());
}
return SendAsync<Artifact>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
queryParameters: queryParams,
userState: userState,
cancellationToken: cancellationToken);
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace GitHub.Actions.Pipelines.WebApi
{
/// <summary>
/// Parses known enum flags in a comma-separated string. Unknown flags are ignored. Allows for degraded compatibility without serializing enums to integer values.
/// </summary>
/// <remarks>
/// Case insensitive. Both standard and EnumMemberAttribute names are parsed.
/// json deserialization doesn't happen for query parameters :)
/// </remarks>
public class KnownFlagsEnumTypeConverter : EnumConverter
{
public KnownFlagsEnumTypeConverter(Type type)
: base(type)
{
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
/// <exception cref="FormatException">Thrown if a flag name is empty.</exception>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string stringValue)
{
try
{
return FlagsEnum.ParseKnownFlags(EnumType, stringValue);
}
catch (Exception ex)
{
// Matches the exception type thrown by EnumTypeConverter.
throw new FormatException(PipelinesWebApiResources.InvalidFlagsEnumValue(stringValue, EnumType), ex);
}
}
return base.ConvertFrom(context, culture, value);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Net.Http;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Actions.Pipelines.WebApi
{
[ResourceArea(PipelinesArea.IdString)]
public class PipelinesHttpClient : PipelinesHttpClientBase
{
public PipelinesHttpClient(Uri baseUrl, VssCredentials credentials)
: base(baseUrl, credentials)
{
}
public PipelinesHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings)
: base(baseUrl, credentials, settings)
{
}
public PipelinesHttpClient(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, handlers)
{
}
public PipelinesHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings, params DelegatingHandler[] handlers)
: base(baseUrl, credentials, settings, handlers)
{
}
public PipelinesHttpClient(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler)
: base(baseUrl, pipeline, disposeHandler)
{
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
namespace GitHub.Actions.Pipelines.WebApi
{
public static class PipelinesArea
{
public const string Name = "pipelines";
public const string IdString = "2e0bf237-8973-4ec9-a581-9c3d679d1776";
public static readonly Guid Id = new Guid(PipelinesArea.IdString);
}
public static class PipelinesResources
{
public static class Artifacts
{
public const string Name = "artifacts";
public static readonly Guid Id = new Guid("85023071-BD5E-4438-89B0-2A5BF362A19D");
}
public static class PipelineOrgs
{
public const string Name = "orgs";
public static readonly Guid Id = new Guid("CD70BA1A-D59A-4E0B-9934-97998159CCC8");
}
public static class Logs
{
public const string Name = "logs";
public static readonly Guid Id = new Guid("fb1b6d27-3957-43d5-a14b-a2d70403e545");
}
public static class Pipelines
{
public const string Name = "pipelines";
public static readonly Guid Id = new Guid("28e1305e-2afe-47bf-abaf-cbb0e6a91988");
}
public static class Reputations
{
public const string Name = "reputations";
public static readonly Guid Id = new Guid("ABA353B0-46FB-4885-88C5-391C6B6382B3");
}
public static class Runs
{
public const string Name = "runs";
public static readonly Guid Id = new Guid("7859261e-d2e9-4a68-b820-a5d84cc5bb3d");
}
public static class SignalR
{
public const string Name = "signalr";
public static readonly Guid Id = new Guid("1FFE4916-AC72-4566-ADD0-9BAB31E44FCF");
}
public static class SignedArtifactsContent
{
public const string Name = "signedartifactscontent";
public static readonly Guid Id = new Guid("6B2AC16F-CD00-4DF9-A13B-3A1CC8AFB188");
}
public static class SignedLogContent
{
public const string Name = "signedlogcontent";
public static readonly Guid Id = new Guid("74f99e32-e2c4-44f4-93dc-dec0bca530a5");
}
public static class SignalRLive
{
public const string Name = "live";
public static readonly Guid Id = new Guid("C41B3775-6D50-48BD-B261-42DA7F0F1BA0");
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Runtime.Serialization;
namespace GitHub.Actions.Pipelines.WebApi
{
public static class UnknownEnum
{
public static T Parse<T>(string stringValue)
{
return (T)Parse(typeof(T), stringValue);
}
public static object Parse(Type enumType, string stringValue)
{
var underlyingType = Nullable.GetUnderlyingType(enumType);
enumType = underlyingType != null ? underlyingType : enumType;
var names = Enum.GetNames(enumType);
if (!string.IsNullOrEmpty(stringValue))
{
var match = names.FirstOrDefault(name => string.Equals(name, stringValue, StringComparison.OrdinalIgnoreCase));
if (match != null)
{
return Enum.Parse(enumType, match);
}
// maybe we have an enum member with an EnumMember attribute specifying a custom name
foreach (var field in enumType.GetFields())
{
var enumMemberAttribute = field.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault() as EnumMemberAttribute;
if (enumMemberAttribute != null && string.Equals(enumMemberAttribute.Value, stringValue, StringComparison.OrdinalIgnoreCase))
{
// we already have the field, no need to do enum.parse on it
return field.GetValue(null);
}
}
}
return Enum.Parse(enumType, UnknownName);
}
private const string UnknownName = "Unknown";
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace GitHub.Actions.Pipelines.WebApi
{
public class UnknownEnumJsonConverter : StringEnumConverter
{
public UnknownEnumJsonConverter()
{
this.CamelCaseText = true;
}
public override bool CanConvert(Type objectType)
{
// we require one member to be named "Unknown"
return objectType.IsEnum && Enum.GetNames(objectType).Any(name => string.Equals(name, UnknownName, StringComparison.OrdinalIgnoreCase));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Newtonsoft doesn't call CanConvert if you specify the converter using a JsonConverter attribute
// they just assume you know what you're doing :)
if (!CanConvert(objectType))
{
// if there's no Unknown value, fall back to the StringEnumConverter behavior
return base.ReadJson(reader, objectType, existingValue, serializer);
}
if (reader.TokenType == JsonToken.Integer)
{
var intValue = Convert.ToInt32(reader.Value);
var values = (int[])Enum.GetValues(objectType);
if (values.Contains(intValue))
{
return Enum.Parse(objectType, intValue.ToString());
}
}
if (reader.TokenType == JsonToken.String)
{
var stringValue = reader.Value.ToString();
return UnknownEnum.Parse(objectType, stringValue);
}
// we know there's an Unknown value because CanConvert returned true
return Enum.Parse(objectType, UnknownName);
}
private const string UnknownName = "Unknown";
}
}

View File

@@ -0,0 +1,26 @@
using System.Globalization;
namespace GitHub.Actions.Pipelines.WebApi
{
public static class PipelinesWebApiResources
{
public static string FlagEnumTypeRequired()
{
const string Format = @"Invalid type. An enum type with the Flags attribute must be supplied.";
return Format;
}
public static string InvalidFlagsEnumValue(object arg0, object arg1)
{
const string Format = @"'{0}' is not a valid value for {1}";
return string.Format(CultureInfo.CurrentCulture, Format, arg0, arg1);
}
public static string NonEmptyEnumElementsRequired(object arg0)
{
const string Format = @"Each comma separated enum value must be non-empty: {0}";
return string.Format(CultureInfo.CurrentCulture, Format, arg0);
}
}
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>

View File

@@ -0,0 +1,18 @@
using System;
using System.Runtime.Serialization;
namespace GitHub.Services.WebApi
{
/// <summary>
/// A signed url allowing limited-time anonymous access to private resources.
/// </summary>
[DataContract]
public class SignedUrl
{
[DataMember]
public string Url { get; set; }
[DataMember]
public DateTime SignatureExpires { get; set; }
}
}

View File

@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common.Tests
"win-x86",
"linux-x64",
"linux-arm",
"rhel.6-x64",
"linux-arm64",
"osx-x64"
};

View File

@@ -0,0 +1,175 @@
using System;
using System.Runtime.CompilerServices;
using GitHub.Runner.Common;
using GitHub.Runner.Listener.Configuration;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class ServiceControlManagerL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Service")]
public void CalculateServiceName()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisiskindofalongrunnername1";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
var serviceNameParts = serviceName.Split('.');
// Verify name is 79 characters
Assert.Equal(79, serviceName.Length);
// Verify nothing has been shortened out
Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
Assert.Equal("thisiskindofalongrunnername1", serviceNameParts[3]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Service")]
public void CalculateServiceName80Chars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisiskindofalongrunnername12";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
// Verify name is still equal to 80 characters
Assert.Equal(80, serviceName.Length);
var serviceNameParts = serviceName.Split('.');
// Verify nothing has been shortened out
Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
Assert.Equal("thisiskindofalongrunnername12", serviceNameParts[3]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Service")]
public void CalculateServiceNameLimitsServiceNameTo80Chars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisisareallyreallylongbutstillvalidagentname";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
// Verify name has been shortened to 80 characters
Assert.Equal(80, serviceName.Length);
var serviceNameParts = serviceName.Split('.');
// Verify that each component has been shortened to a sensible length
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-'
Assert.Equal("thisisareallyreally", serviceNameParts[3]); // Remainder of unused chars
}
}
// Special 'defensive' test that verifies we can gracefully handle creating service names
// in case GitHub.com changes its org/repo naming convention in the future,
// and some of these characters may be invalid for service names
// Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Service")]
public void CalculateServiceNameSanitizeOutOfRangeChars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "name";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
var serviceNameParts = serviceName.Split('.');
// Verify service name parts are sanitized correctly
Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
Assert.Equal("name", serviceNameParts[3]);
}
}
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{
TestHostContext hc = new TestHostContext(this, testName);
return hc;
}
}
}

View File

@@ -209,7 +209,7 @@ namespace GitHub.Runner.Common.Tests.Worker
Id = actionId,
Reference = new Pipelines.RepositoryPathReference()
{
Name = "actions/npm",
Name = "actions/download-artifact",
Ref = "master",
RepositoryType = "GitHub"
}
@@ -220,12 +220,12 @@ namespace GitHub.Runner.Common.Tests.Worker
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
//Assert
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/npm", "master.completed");
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/download-artifact", "master.completed");
Assert.True(File.Exists(watermarkFile));
var actionDockerfile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/npm", "master", "Dockerfile");
Assert.True(File.Exists(actionDockerfile));
_hc.GetTrace().Info(File.ReadAllText(actionDockerfile));
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/download-artifact", "master", "action.yml");
Assert.True(File.Exists(actionYamlFile));
_hc.GetTrace().Info(File.ReadAllText(actionYamlFile));
}
finally
{

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using GitHub.Runner.Worker;
using GitHub.Services.WebApi;
using Xunit;
@@ -353,6 +350,48 @@ namespace GitHub.Runner.Common.Tests.Worker
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_DefaultSeverity()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""severity"": ""warning"",
""pattern"": [
{
""regexp"": ""^(ERROR)?(?: )?(.+):$"",
""severity"": 1,
""code"": 2
},
{
""regexp"": ""^(.+)$"",
""message"": 1
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("ABC:");
match = matcher.Match("not-working");
Assert.Equal("warning", match.Severity);
Assert.Equal("ABC", match.Code);
Assert.Equal("not-working", match.Message);
match = matcher.Match("ERROR ABC:");
match = matcher.Match("not-working");
Assert.Equal("ERROR", match.Severity);
Assert.Equal("ABC", match.Code);
Assert.Equal("not-working", match.Message);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
@@ -754,6 +793,43 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal("myMatcher", matcher.Owner);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_SinglePattern_DefaultSeverity()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""severity"": ""warning"",
""pattern"": [
{
""regexp"": ""^(ERROR)?(?: )?(.+): (.+)$"",
""severity"": 1,
""code"": 2,
""message"": 3
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("ABC: not-working");
Assert.Equal("warning", match.Severity);
Assert.Equal("ABC", match.Code);
Assert.Equal("not-working", match.Message);
match = matcher.Match("ERROR ABC: not-working");
Assert.Equal("ERROR", match.Severity);
Assert.Equal("ABC", match.Code);
Assert.Equal("not-working", match.Message);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603;NU1603;xUnit2013;</NoWarn>

View File

@@ -10,6 +10,7 @@ set -e
DEV_CMD=$1
DEV_CONFIG=$2
DEV_TARGET_RUNTIME=$3
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAYOUT_DIR="$SCRIPT_DIR/../_layout"
@@ -43,24 +44,20 @@ elif [[ "$CURRENT_PLATFORM" == 'linux' ]]; then
CPU_NAME=$(uname -m)
case $CPU_NAME in
armv7l) RUNTIME_ID="linux-arm";;
aarch64) RUNTIME_ID="linux-arm";;
aarch64) RUNTIME_ID="linux-arm64";;
esac
fi
if [ -e /etc/redhat-release ]; then
redhatRelease=$(</etc/redhat-release)
if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux Server release 6."* ]]; then
RUNTIME_ID='rhel.6-x64'
fi
fi
elif [[ "$CURRENT_PLATFORM" == 'darwin' ]]; then
RUNTIME_ID='osx-x64'
fi
if [[ -n "$DEV_TARGET_RUNTIME" ]]; then
RUNTIME_ID="$DEV_TARGET_RUNTIME"
fi
# Make sure current platform support publish the dotnet runtime
# Windows can publish win-x86/x64
# Linux can publish linux-x64/arm/rhel.6-x64
# Linux can publish linux-x64/arm/arm64
# OSX can publish osx-x64
if [[ "$CURRENT_PLATFORM" == 'windows' ]]; then
if [[ ("$RUNTIME_ID" != 'win-x86') && ("$RUNTIME_ID" != 'win-x64') ]]; then
@@ -68,7 +65,7 @@ if [[ "$CURRENT_PLATFORM" == 'windows' ]]; then
exit 1
fi
elif [[ "$CURRENT_PLATFORM" == 'linux' ]]; then
if [[ ("$RUNTIME_ID" != 'linux-x64') && ("$RUNTIME_ID" != 'linux-arm') && ("$RUNTIME_ID" != 'rhel.6-x64') ]]; then
if [[ ("$RUNTIME_ID" != 'linux-x64') && ("$RUNTIME_ID" != 'linux-x86') && ("$RUNTIME_ID" != 'linux-arm64') && ("$RUNTIME_ID" != 'linux-arm') ]]; then
echo "Failed: Can't build $RUNTIME_ID package $CURRENT_PLATFORM" >&2
exit 1
fi
@@ -151,7 +148,8 @@ function package ()
echo "You must build first. Expecting to find ${LAYOUT_DIR}/bin"
fi
runner_ver=$("${LAYOUT_DIR}/bin/Runner.Listener" --version) || failed "version"
# TODO: We are cross-compiling arm on x64 so we cant exec Runner.Listener. Remove after building on native arm host
runner_ver=$("${LAYOUT_DIR}/bin/Runner.Listener" --version) || runner_ver=$(cat runnerversion) || failed "version"
runner_pkg_name="actions-runner-${RUNTIME_ID}-${runner_ver}"
heading "Packaging ${runner_pkg_name}"

View File

@@ -1 +1 @@
2.160.0
2.162.0

View File

@@ -1,9 +1,12 @@
parameters:
targetRuntime: ''
steps:
# Build agent layout
- script: dev.cmd layout Release
- script: dev.cmd layout Release ${{ parameters.targetRuntime }}
workingDirectory: src
displayName: Build & Layout Release
displayName: Build & Layout Release ${{ parameters.targetRuntime }}
# Run test
- script: dev.cmd test