mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
39 Commits
Duplicated
...
v2.308.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f7ff150c6 | ||
|
|
a8783c023f | ||
|
|
2606425cc5 | ||
|
|
8fb038b0e0 | ||
|
|
8b30f9381b | ||
|
|
8206cf4e73 | ||
|
|
6680a3b142 | ||
|
|
b882f6696a | ||
|
|
e76de55cda | ||
|
|
9eb4b96713 | ||
|
|
719348e0bf | ||
|
|
9fe5aa2a9a | ||
|
|
765a5c3efc | ||
|
|
e752edf7b5 | ||
|
|
e350f35217 | ||
|
|
8fa970a1e6 | ||
|
|
8eefd849c1 | ||
|
|
f6e9809844 | ||
|
|
5b2e4049bc | ||
|
|
7cb61925b0 | ||
|
|
a61d3f37dc | ||
|
|
e30b9d6d12 | ||
|
|
496904c0b7 | ||
|
|
b91ad56f92 | ||
|
|
f25c9dfba3 | ||
|
|
7d432fb24c | ||
|
|
e8ee6f7b1b | ||
|
|
d4bbbb8419 | ||
|
|
4ffd081aea | ||
|
|
c05e6748c3 | ||
|
|
a2b7856c9c | ||
|
|
5f1c6f4708 | ||
|
|
8415f13bab | ||
|
|
471e3ae2d9 | ||
|
|
1096b975e4 | ||
|
|
282ba4cfc8 | ||
|
|
b737a5ac5c | ||
|
|
20721bc950 | ||
|
|
fde86b0666 |
@@ -5,7 +5,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.405"
|
||||
"version": "6.0.412"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
|
||||
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/images"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "/src"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/Misc/expressionFunc/hashFiles"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: production # check only dependencies, which are going to the compiled app, not supporting tools like @vue-cli
|
||||
3
.github/workflows/publish-image.yml
vendored
3
.github/workflows/publish-image.yml
vendored
@@ -53,6 +53,9 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -699,6 +699,9 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
17
.github/workflows/stale-bot.yml
vendored
Normal file
17
.github/workflows/stale-bot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: ‘Close stale Runner issues’
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # every monday at midnight
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
stale-issue-message: ‘This issue is stale because it has been open 365 days with no activity. Remove stale label or comment or this will be closed in 15 days.’
|
||||
close-issue-message: ‘This issue was closed because it has been stalled for 15 days with no activity.’
|
||||
exempt-issue-labels: ‘keep’
|
||||
days-before-stale: 365
|
||||
days-before-close: 15
|
||||
debug-only: true
|
||||
@@ -1,14 +1,18 @@
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
|
||||
# Source: https://github.com/dotnet/dotnet-docker
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy as build
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_ARCH="x64"
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.3.2
|
||||
ARG DOCKER_VERSION=20.10.23
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
WORKDIR /actions-runner
|
||||
RUN curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
|
||||
RUN export RUNNER_ARCH=${TARGETARCH} \
|
||||
&& if [ "$RUNNER_ARCH" = "amd64" ]; then export RUNNER_ARCH=x64 ; fi \
|
||||
&& curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${TARGETOS}-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
|
||||
&& tar xzf ./runner.tar.gz \
|
||||
&& rm runner.tar.gz
|
||||
|
||||
@@ -16,13 +20,14 @@ RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-c
|
||||
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
||||
&& rm runner-container-hooks.zip
|
||||
|
||||
RUN export DOCKER_ARCH=x86_64 \
|
||||
RUN export RUNNER_ARCH=${TARGETARCH} \
|
||||
&& if [ "$RUNNER_ARCH" = "amd64" ]; then export DOCKER_ARCH=x86_64 ; fi \
|
||||
&& if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
|
||||
&& curl -fLo docker.tgz https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
||||
&& curl -fLo docker.tgz https://download.docker.com/${TARGETOS}/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
||||
&& tar zxvf docker.tgz \
|
||||
&& rm -rf docker.tgz
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||
@@ -31,6 +36,7 @@ ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
sudo \
|
||||
lsb-release \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
## Features
|
||||
- Runner changes for communication with Results service (#2510, #2531, #2535, #2516)
|
||||
- Add `*.ghe.localhost` domains to hosted server check (#2536)
|
||||
- Add `OrchestrationId` to user-agent for better telemetry correlation. (#2568)
|
||||
|
||||
## Bugs
|
||||
- Fix JIT configurations on Windows (#2497)
|
||||
- Guard against NullReference while creating HostContext (#2343)
|
||||
- Handles broken symlink in `Which` (#2150, #2196)
|
||||
- Adding curl retry for external tool downloads (#2552, #2557)
|
||||
- Limit the time we wait for waiting websocket to connect. (#2554)
|
||||
- Support linux/arm64 docker build (#2601)
|
||||
- Add node20 to runner (#2732)
|
||||
- Update node16 to latest version (#2736)
|
||||
- Remove node12 from runner (#2717)
|
||||
|
||||
## Misc
|
||||
- Bump container hooks version to 0.3.1 in runner image (#2496)
|
||||
- Runner changes to communicate with vNext services (#2487, #2500, #2505, #2541, #2547)
|
||||
- Pass timeout in ExecutionContext instead of StepsRunner (#2714)
|
||||
- Return early on invalid_client OAuth exception (#2721)
|
||||
- Expose results service endpoint as environment variable (#2726)
|
||||
- Update HTTPEventSourceListener to trace the right events (#2727)
|
||||
- Change RunnerId/AgentId from int32 to uint64 (#2661)
|
||||
- Configure stale bot for Runner (#2729)
|
||||
- Add in dependabot security scanning/updates (#2743)
|
||||
- Bump dotnet sdk to latest version (#2733)
|
||||
- Switch from InnerException to ErrorCode on disableupdate check (#2718)
|
||||
|
||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
|
||||
@@ -1 +1 @@
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
2.308.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
39f2a931565d6a10e695ac8ed14bb9dcbb568151410349b32dbf9c27bae29602
|
||||
7b78ca2997fbe048642d3717ab7321cdd359752b97158f3c67eb3df8786e21d3
|
||||
@@ -1 +1 @@
|
||||
29ffb303537d8ba674fbebc7729292c21c4ebd17b3198f91ed593ef4cbbb67b5
|
||||
6f34c1d501c87c2e22c2278df7152999aca628c66ee4176d32325773487da6d7
|
||||
@@ -1 +1 @@
|
||||
de6868a836fa3cb9e5ddddbc079da1c25e819aa2d2fc193cc9931c353687c57c
|
||||
921ca58050be56e0b84af05e544cab4a151cb66405e815e19c0e0928ef7313f5
|
||||
@@ -1 +1 @@
|
||||
339d3e1a5fd28450c0fe6cb820cc7aae291f0f9e2d153ac34e1f7b080e35d30e
|
||||
50f5c147074fc4943b4198b2d9b57c5e94344ab21350b0880ec8e2b85d27152b
|
||||
@@ -1 +1 @@
|
||||
dcb7f606c1d7d290381e5020ee73e7f16dcbd2f20ac9b431362ccbb5120d449c
|
||||
16269548335b1f2add41a409aa3558c56581b63f280a9a26956707b6370558bd
|
||||
@@ -1 +1 @@
|
||||
1bbcb0e9a2cf4be4b1fce77458de139b70ac58efcbb415a6db028b9373ae1673
|
||||
e4aa6003ec77a2b21f3021927fed48727bde379fafff300f39565ff2fff4dd87
|
||||
@@ -1 +1 @@
|
||||
44cd25f3c104d0abb44d262397a80e0b2c4f206465c5d899a22eec043dac0fb3
|
||||
16ab4c166c58bc4c5600ff055be7ce0a9bb0dd993388114a76efea51e4ea14cb
|
||||
2
src/Misc/contentHash/externals/linux-arm
vendored
2
src/Misc/contentHash/externals/linux-arm
vendored
@@ -1 +1 @@
|
||||
3807dcbf947e840c33535fb466b096d76bf09e5c0254af8fc8cbbb24c6388222
|
||||
5bdddd32bab1e57af252b470579083049496e9e39b6e4f50de01232581f9a2d8
|
||||
2
src/Misc/contentHash/externals/linux-arm64
vendored
2
src/Misc/contentHash/externals/linux-arm64
vendored
@@ -1 +1 @@
|
||||
ee01eee80cd8a460a4b9780ee13fdd20f25c59e754b4ccd99df55fbba2a85634
|
||||
54b3b3a72da93db0fa38708c759fceadddb70cacdd3620a079084a242126dd78
|
||||
2
src/Misc/contentHash/externals/linux-x64
vendored
2
src/Misc/contentHash/externals/linux-x64
vendored
@@ -1 +1 @@
|
||||
a9fb9c14e24e79aec97d4da197dd7bfc6364297d6fce573afb2df48cc9a931f8
|
||||
e7f2da271abb174285c3a757503538b3e9792e9d731b0382b6d1f21bb59a79ba
|
||||
2
src/Misc/contentHash/externals/osx-arm64
vendored
2
src/Misc/contentHash/externals/osx-arm64
vendored
@@ -1 +1 @@
|
||||
a4e0e8fc62eba0967a39c7d693dcd0aeb8b2bed0765f9c38df80d42884f65341
|
||||
2481c5b0d06b2b5621635f2568b86a43b0e5b259fed1298167ba4f33d4c464c7
|
||||
2
src/Misc/contentHash/externals/osx-x64
vendored
2
src/Misc/contentHash/externals/osx-x64
vendored
@@ -1 +1 @@
|
||||
17ac17fbe785b3d6fa2868d8d17185ebfe0c90b4b0ddf6b67eac70e42bcd989b
|
||||
85de7677165e65ec69b8a9e344c0811efa51b7fe5376a1aa083505c560ea6f57
|
||||
2
src/Misc/contentHash/externals/win-arm64
vendored
2
src/Misc/contentHash/externals/win-arm64
vendored
@@ -1 +1 @@
|
||||
89f24657a550f1e818b0e9975e5b80edcf4dd22b7d4bccbb9e48e37f45d30fb1
|
||||
763d18de11c11fd299c0e75e98fefc8a0e6605ae0ad6aba3bbc110db2262ab41
|
||||
2
src/Misc/contentHash/externals/win-x64
vendored
2
src/Misc/contentHash/externals/win-x64
vendored
@@ -1 +1 @@
|
||||
24fd131b5dce33ef16038b771407bc0507da8682a72fb3b7780607235f76db0b
|
||||
16f3cc545dfe10e84df43746073fc64d3c44d1891782532805aeb2118869a55d
|
||||
3061
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
3061
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,11 +25,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.12",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.6.4"
|
||||
"eslint-plugin-github": "^4.9.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"prettier": "^3.0.1",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ PRECACHE=$2
|
||||
|
||||
NODE_URL=https://nodejs.org/dist
|
||||
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
||||
NODE12_VERSION="12.22.7"
|
||||
NODE16_VERSION="16.16.0"
|
||||
NODE16_VERSION="16.20.1"
|
||||
NODE20_VERSION="20.5.0"
|
||||
# used only for win-arm64, remove node16 unofficial version when official version is available
|
||||
NODE16_UNOFFICIAL_VERSION="16.20.0"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
@@ -137,10 +139,10 @@ function acquireExternalTool() {
|
||||
|
||||
# Download the external tools only for Windows.
|
||||
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
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||
fi
|
||||
@@ -149,8 +151,10 @@ fi
|
||||
# Download the external tools only for Windows.
|
||||
if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
|
||||
# todo: replace these with official release when available
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||
fi
|
||||
@@ -158,29 +162,30 @@ fi
|
||||
|
||||
# Download the external tools only for OSX.
|
||||
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-darwin-x64.tar.gz" node12 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-x64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||
# node.js v12 doesn't support macOS on arm64.
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-arm64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
# 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
|
||||
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE12_VERSION}/alpine/x64/node-v${NODE12_VERSION}-alpine-x64.tar.gz" node12_alpine
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE16_VERSION}/alpine/x64/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir
|
||||
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE20_VERSION}/alpine/x64/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_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
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-arm64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-armv7l.tar.gz" node12 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-armv7l.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-armv7l.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
@@ -75,6 +75,7 @@ mscordaccore.dll
|
||||
mscordaccore_amd64_amd64_6.0.522.21309.dll
|
||||
mscordaccore_arm64_arm64_6.0.522.21309.dll
|
||||
mscordaccore_amd64_amd64_6.0.1322.58009.dll
|
||||
mscordaccore_amd64_amd64_6.0.2023.32017.dll
|
||||
mscordbi.dll
|
||||
mscorlib.dll
|
||||
mscorrc.debug.dll
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common
|
||||
private bool? _isHostedServer;
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public int AgentId { get; set; }
|
||||
public ulong AgentId { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string AgentName { get; set; }
|
||||
|
||||
@@ -154,7 +154,8 @@ namespace GitHub.Runner.Common
|
||||
public static class Features
|
||||
{
|
||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
|
||||
public static readonly string Node16Warning = "DistributedTask.AddWarningToNode16Action";
|
||||
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||
}
|
||||
@@ -170,7 +171,8 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
|
||||
public static readonly string UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string SummaryUploadError = "$GITHUB_STEP_SUMMARY upload aborted, an error occurred when uploading the summary. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: {0}. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/.";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLife = "The following actions uses node12 which is deprecated and will be forced to run on node16: {0}. For more info: https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLifeEnvVariable = "Node16ForceActionsWarnings";
|
||||
}
|
||||
|
||||
public static class RunnerEvent
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace GitHub.Runner.Common
|
||||
Dictionary<String, VariableValue> outputs,
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
CancellationToken token);
|
||||
|
||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||
@@ -70,11 +71,12 @@ namespace GitHub.Runner.Common
|
||||
Dictionary<String, VariableValue> outputs,
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest(
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, cancellationToken), cancellationToken);
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace GitHub.Runner.Common
|
||||
|
||||
// Configuration
|
||||
Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent);
|
||||
Task DeleteAgentAsync(int agentPoolId, int agentId);
|
||||
Task DeleteAgentAsync(int agentId);
|
||||
Task DeleteAgentAsync(int agentPoolId, ulong agentId);
|
||||
Task DeleteAgentAsync(ulong agentId);
|
||||
Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation);
|
||||
Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null);
|
||||
Task<List<TaskAgent>> GetAgentsAsync(string agentName);
|
||||
@@ -50,7 +50,7 @@ namespace GitHub.Runner.Common
|
||||
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
|
||||
|
||||
// agent update
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState, string trace);
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace);
|
||||
}
|
||||
|
||||
public sealed class RunnerServer : RunnerService, IRunnerServer
|
||||
@@ -239,13 +239,13 @@ namespace GitHub.Runner.Common
|
||||
return _genericTaskAgentClient.ReplaceAgentAsync(agentPoolId, agent);
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(int agentPoolId, int agentId)
|
||||
public Task DeleteAgentAsync(int agentPoolId, ulong agentId)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(int agentId)
|
||||
public Task DeleteAgentAsync(ulong agentId)
|
||||
{
|
||||
return DeleteAgentAsync(0, agentId); // agentPool is ignored server side
|
||||
}
|
||||
@@ -315,7 +315,7 @@ namespace GitHub.Runner.Common
|
||||
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState, string trace)
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
|
||||
|
||||
@@ -6,13 +6,7 @@ namespace GitHub.Runner.Common.Util
|
||||
public static class NodeUtil
|
||||
{
|
||||
private const string _defaultNodeVersion = "node16";
|
||||
|
||||
#if (OS_OSX || OS_WINDOWS) && ARM64
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16" });
|
||||
#else
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node12", "node16" });
|
||||
#endif
|
||||
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16", "node20" });
|
||||
public static string GetInternalNodeVersion()
|
||||
{
|
||||
var forcedInternalNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -351,21 +351,39 @@ namespace GitHub.Runner.Listener.Check
|
||||
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new()
|
||||
{
|
||||
{
|
||||
"Microsoft-System-Net-Http",
|
||||
"System.Net.Http",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Info",
|
||||
"Associate",
|
||||
"Enter",
|
||||
"Exit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Microsoft-System-Net-Security",
|
||||
"System.Net.Security",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Info",
|
||||
"DumpBuffer",
|
||||
"SslStreamCtor",
|
||||
"SecureChannelCtor",
|
||||
"NoDelegateNoClientCert",
|
||||
"CertsAfterFiltering",
|
||||
"UsingCachedCredential",
|
||||
"SspiSelectedCipherSuite"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Private.InternalDiagnostics.System.Net.Http",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Info",
|
||||
"Associate",
|
||||
}
|
||||
},
|
||||
{
|
||||
"Private.InternalDiagnostics.System.Net.Security",
|
||||
new HashSet<string>
|
||||
{
|
||||
"Enter",
|
||||
"Exit",
|
||||
"Info",
|
||||
"DumpBuffer",
|
||||
"SslStreamCtor",
|
||||
@@ -391,8 +409,8 @@ namespace GitHub.Runner.Listener.Check
|
||||
{
|
||||
base.OnEventSourceCreated(eventSource);
|
||||
|
||||
if (eventSource.Name == "Microsoft-System-Net-Http" ||
|
||||
eventSource.Name == "Microsoft-System-Net-Security")
|
||||
if (eventSource.Name.Contains("System.Net.Http") ||
|
||||
eventSource.Name.Contains("System.Net.Security"))
|
||||
{
|
||||
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All);
|
||||
}
|
||||
|
||||
@@ -1077,23 +1077,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
if (this._isRunServiceJob)
|
||||
{
|
||||
var runServer = await GetRunServerAsync(systemConnection);
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||
var unhandledAnnotation = unhandledExceptionIssue.ToAnnotation();
|
||||
var jobAnnotations = new List<Annotation>();
|
||||
if (unhandledAnnotation.HasValue)
|
||||
{
|
||||
jobAnnotations.Add(unhandledAnnotation.Value);
|
||||
}
|
||||
try
|
||||
{
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to raise job completion back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
Trace.Verbose($"Skip CompleteJobRequestAsync call from Listener because it's RunService job");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1193,7 +1177,7 @@ namespace GitHub.Runner.Listener
|
||||
jobAnnotations.Add(unhandledAnnotation.Value);
|
||||
}
|
||||
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, CancellationToken.None);
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -123,8 +123,15 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error("Catch exception during create session.");
|
||||
Trace.Error(ex);
|
||||
|
||||
if (ex is VssOAuthTokenRequestException && creds.Federated is VssOAuthCredential vssOAuthCred)
|
||||
if (ex is VssOAuthTokenRequestException vssOAuthEx && creds.Federated is VssOAuthCredential vssOAuthCred)
|
||||
{
|
||||
// "invalid_client" means the runner registration has been deleted from the server.
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
// If the runner registration get deleted, we can't exchange oauth token.
|
||||
Trace.Error("Test oauth app registration.");
|
||||
@@ -245,7 +252,7 @@ namespace GitHub.Runner.Listener
|
||||
_accessTokenRevoked = true;
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
{
|
||||
terminal.WriteError($"An error occured: {e.Message}");
|
||||
trace.Error(e);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Listener
|
||||
private ITerminal _terminal;
|
||||
private IRunnerServer _runnerServer;
|
||||
private int _poolId;
|
||||
private int _agentId;
|
||||
private ulong _agentId;
|
||||
private readonly ConcurrentQueue<string> _updateTrace = new();
|
||||
private Task _cloneAndCalculateContentHashTask;
|
||||
private string _dotnetRuntimeCloneDirectory;
|
||||
|
||||
@@ -69,6 +69,10 @@ namespace GitHub.Runner.Sdk
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(httpProxyAddress) && !Uri.TryCreate(httpProxyAddress, UriKind.Absolute, out var _))
|
||||
{
|
||||
httpProxyAddress = PrependHttpIfMissing(httpProxyAddress);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(httpProxyAddress) && Uri.TryCreate(httpProxyAddress, UriKind.Absolute, out var proxyHttpUri))
|
||||
{
|
||||
_httpProxyAddress = proxyHttpUri.OriginalString;
|
||||
@@ -99,6 +103,10 @@ namespace GitHub.Runner.Sdk
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(httpsProxyAddress) && !Uri.TryCreate(httpsProxyAddress, UriKind.Absolute, out var _))
|
||||
{
|
||||
httpsProxyAddress = PrependHttpIfMissing(httpsProxyAddress);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(httpsProxyAddress) && Uri.TryCreate(httpsProxyAddress, UriKind.Absolute, out var proxyHttpsUri))
|
||||
{
|
||||
_httpsProxyAddress = proxyHttpsUri.OriginalString;
|
||||
@@ -240,5 +248,20 @@ namespace GitHub.Runner.Sdk
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string PrependHttpIfMissing(string proxyAddress)
|
||||
{
|
||||
// much like in golang, see https://github.com/golang/net/blob/f5464ddb689c015d1abf4df78a806a54af977e6c/http/httpproxy/proxy.go#LL156C31-L156C31
|
||||
if (!proxyAddress.StartsWith("http://", StringComparison.Ordinal) && !proxyAddress.StartsWith("https://", StringComparison.Ordinal))
|
||||
{
|
||||
var prependedProxyAddress = "http://" + proxyAddress;
|
||||
if (Uri.TryCreate(prependedProxyAddress, UriKind.Absolute, out var _))
|
||||
{
|
||||
// if prepending http:// turns the proxyAddress into a valid Uri, then use that
|
||||
return prependedProxyAddress;
|
||||
}
|
||||
}
|
||||
return proxyAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace GitHub.Runner.Sdk
|
||||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
|
||||
{
|
||||
settings.AllowAutoRedirect = true;
|
||||
settings.AllowAutoRedirectForBroker = true;
|
||||
}
|
||||
|
||||
// Remove Invariant from the list of accepted languages.
|
||||
|
||||
@@ -1042,7 +1042,7 @@ namespace GitHub.Runner.Worker
|
||||
if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Container)
|
||||
{
|
||||
var containerAction = actionDefinitionData.Execution as ContainerActionExecutionData;
|
||||
if (containerAction.Image.EndsWith("Dockerfile") || containerAction.Image.EndsWith("dockerfile"))
|
||||
if (DockerUtil.IsDockerfile(containerAction.Image))
|
||||
{
|
||||
var dockerFileFullPath = Path.Combine(actionEntryDirectory, containerAction.Image);
|
||||
executionContext.Debug($"Dockerfile for action: '{dockerFileFullPath}'.");
|
||||
@@ -1127,8 +1127,16 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
else
|
||||
{
|
||||
var fullPath = IOUtil.ResolvePath(actionEntryDirectory, "."); // resolve full path without access filesystem.
|
||||
throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
||||
var reference = repositoryReference.Name;
|
||||
if (!string.IsNullOrEmpty(repositoryReference.Path))
|
||||
{
|
||||
reference = $"{reference}/{repositoryReference.Path}";
|
||||
}
|
||||
if (!string.IsNullOrEmpty(repositoryReference.Ref))
|
||||
{
|
||||
reference = $"{reference}@{repositoryReference.Ref}";
|
||||
}
|
||||
throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' for action '{reference}'.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace GitHub.Runner.Worker
|
||||
foreach (var error in templateContext.Errors)
|
||||
{
|
||||
Trace.Error($"Action.yml load error: {error.Message}");
|
||||
executionContext.Error("And let's duplicate errors: " + error.Message);
|
||||
executionContext.Error(error.Message);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Fail to load {fileRelativePath}");
|
||||
@@ -449,7 +449,8 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase))
|
||||
string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(usingToken.Value, "node20", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(mainToken?.Value))
|
||||
{
|
||||
@@ -489,7 +490,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12' or 'node16' instead.");
|
||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead.");
|
||||
}
|
||||
}
|
||||
else if (pluginToken != null)
|
||||
@@ -500,7 +501,7 @@ namespace GitHub.Runner.Worker
|
||||
};
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.");
|
||||
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.");
|
||||
}
|
||||
|
||||
private void ConvertInputs(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GitHub.Runner.Worker.Container
|
||||
@@ -65,6 +66,16 @@ namespace GitHub.Runner.Worker.Container
|
||||
return "";
|
||||
}
|
||||
|
||||
public static bool IsDockerfile(string image)
|
||||
{
|
||||
if (image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var imageWithoutPath = image.Split('/').Last();
|
||||
return imageWithoutPath.StartsWith("Dockerfile.", StringComparison.OrdinalIgnoreCase) || imageWithoutPath.EndsWith("Dockerfile", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string CreateEscapedOption(string flag, string key)
|
||||
{
|
||||
if (String.IsNullOrEmpty(key))
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace GitHub.Runner.Worker
|
||||
// \_layout\_work\_temp\[jobname-support]\files\environment.txt
|
||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||
RunnerSettings settings = configurationStore.GetSettings();
|
||||
int runnerId = settings.AgentId;
|
||||
ulong runnerId = settings.AgentId;
|
||||
string runnerName = settings.AgentName;
|
||||
int poolId = settings.PoolId;
|
||||
|
||||
|
||||
@@ -78,11 +78,12 @@ namespace GitHub.Runner.Worker
|
||||
List<string> StepEnvironmentOverrides { get; }
|
||||
|
||||
ExecutionContext Root { get; }
|
||||
ExecutionContext Parent { get; }
|
||||
|
||||
// Initialize
|
||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||
void CancelToken();
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, TimeSpan? timeout = null);
|
||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||
|
||||
// logging
|
||||
@@ -264,6 +265,14 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
public ExecutionContext Parent
|
||||
{
|
||||
get
|
||||
{
|
||||
return _parentExecutionContext;
|
||||
}
|
||||
}
|
||||
|
||||
public JobContext JobContext
|
||||
{
|
||||
get
|
||||
@@ -348,7 +357,8 @@ namespace GitHub.Runner.Worker
|
||||
bool isEmbedded = false,
|
||||
CancellationTokenSource cancellationTokenSource = null,
|
||||
Guid embeddedId = default(Guid),
|
||||
string siblingScopeName = null)
|
||||
string siblingScopeName = null,
|
||||
TimeSpan? timeout = null)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -377,6 +387,12 @@ namespace GitHub.Runner.Worker
|
||||
child.ExpressionFunctions.Add(item);
|
||||
}
|
||||
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
|
||||
if (timeout != null)
|
||||
{
|
||||
// composite steps inherit the timeout from the parent, set by https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes
|
||||
child.SetTimeout(timeout);
|
||||
}
|
||||
|
||||
child.EchoOnActionCommand = EchoOnActionCommand;
|
||||
|
||||
if (recordOrder != null)
|
||||
@@ -406,7 +422,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
/// <summary>
|
||||
/// An embedded execution context shares the same record ID, record name, logger,
|
||||
/// and a linked cancellation token.
|
||||
/// but NOT the cancellation token (just like workflow steps contexts - they don't share a token)
|
||||
/// </summary>
|
||||
public IExecutionContext CreateEmbeddedChild(
|
||||
string scopeName,
|
||||
@@ -416,7 +432,7 @@ namespace GitHub.Runner.Worker
|
||||
Dictionary<string, string> intraActionState = null,
|
||||
string siblingScopeName = null)
|
||||
{
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName, timeout: GetRemainingTimeout());
|
||||
}
|
||||
|
||||
public void Start(string currentOperation = null)
|
||||
@@ -477,28 +493,32 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
PublishStepTelemetry();
|
||||
|
||||
var stepResult = new StepResult
|
||||
if (_record.RecordType == "Task")
|
||||
{
|
||||
ExternalID = _record.Id,
|
||||
Conclusion = _record.Result ?? TaskResult.Succeeded,
|
||||
Status = _record.State,
|
||||
Number = _record.Order,
|
||||
Name = _record.Name,
|
||||
StartedAt = _record.StartTime,
|
||||
CompletedAt = _record.FinishTime,
|
||||
Annotations = new List<Annotation>()
|
||||
};
|
||||
|
||||
_record.Issues?.ForEach(issue =>
|
||||
{
|
||||
var annotation = issue.ToAnnotation();
|
||||
if (annotation != null)
|
||||
var stepResult = new StepResult
|
||||
{
|
||||
stepResult.Annotations.Add(annotation.Value);
|
||||
}
|
||||
});
|
||||
ExternalID = _record.Id,
|
||||
Conclusion = _record.Result ?? TaskResult.Succeeded,
|
||||
Status = _record.State,
|
||||
Number = _record.Order,
|
||||
Name = _record.Name,
|
||||
StartedAt = _record.StartTime,
|
||||
CompletedAt = _record.FinishTime,
|
||||
Annotations = new List<Annotation>()
|
||||
};
|
||||
|
||||
_record.Issues?.ForEach(issue =>
|
||||
{
|
||||
var annotation = issue.ToAnnotation();
|
||||
if (annotation != null)
|
||||
{
|
||||
stepResult.Annotations.Add(annotation.Value);
|
||||
}
|
||||
});
|
||||
|
||||
Global.StepsResult.Add(stepResult);
|
||||
}
|
||||
|
||||
Global.StepsResult.Add(stepResult);
|
||||
|
||||
if (Root != this)
|
||||
{
|
||||
@@ -593,9 +613,33 @@ namespace GitHub.Runner.Worker
|
||||
if (timeout != null)
|
||||
{
|
||||
_cancellationTokenSource.CancelAfter(timeout.Value);
|
||||
m_timeoutStartedAt = DateTime.UtcNow;
|
||||
m_timeout = timeout.Value;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? m_timeoutStartedAt;
|
||||
TimeSpan? m_timeout;
|
||||
public TimeSpan? GetRemainingTimeout()
|
||||
{
|
||||
if (m_timeoutStartedAt != null && m_timeout != null)
|
||||
{
|
||||
var elapsedSinceTimeoutSet = DateTime.UtcNow - m_timeoutStartedAt.Value;
|
||||
var remainingTimeout = m_timeout.Value - elapsedSinceTimeoutSet;
|
||||
if (remainingTimeout.Ticks > 0)
|
||||
{
|
||||
return remainingTimeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
// there was a timeout and it has expired
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
// no timeout was ever set
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Progress(int percentage, string currentOperation = null)
|
||||
{
|
||||
if (percentage > 100 || percentage < 0)
|
||||
@@ -763,9 +807,9 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
Global.Variables = new Variables(HostContext, variables);
|
||||
|
||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
|
||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo16") ?? false)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node12");
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node16");
|
||||
}
|
||||
|
||||
// Environment variables shared across all actions
|
||||
@@ -1355,6 +1399,12 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
foreach (var key in dict.Keys.ToList())
|
||||
{
|
||||
if (key == PipelineTemplateConstants.HostWorkspace)
|
||||
{
|
||||
// The HostWorkspace context var is excluded so that there is a var that always points to the host path.
|
||||
// This var can be used to translate back from container paths, e.g. in HashFilesFunction, which always runs on the host machine
|
||||
continue;
|
||||
}
|
||||
if (dict[key] is StringContextData)
|
||||
{
|
||||
var value = dict[key].ToString();
|
||||
@@ -1401,7 +1451,15 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public void Error(string format, params Object[] args)
|
||||
{
|
||||
_executionContext.Error("error from template trace writer: " + string.Format(CultureInfo.CurrentCulture, format, args));
|
||||
/* TraceWriter should be used for logging and not creating erros. */
|
||||
if (logTemplateErrorsAsDebugMessages())
|
||||
{
|
||||
_executionContext.Debug(string.Format(CultureInfo.CurrentCulture, format, args));
|
||||
}
|
||||
else
|
||||
{
|
||||
_executionContext.Error(string.Format(CultureInfo.CurrentCulture, format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public void Info(string format, params Object[] args)
|
||||
@@ -1414,8 +1472,18 @@ namespace GitHub.Runner.Worker
|
||||
// todo: switch to verbose?
|
||||
_executionContext.Debug(string.Format(CultureInfo.CurrentCulture, $"{format}", args));
|
||||
}
|
||||
|
||||
private bool logTemplateErrorsAsDebugMessages()
|
||||
{
|
||||
if (_executionContext.Global.Variables.TryGetValue(Constants.Runner.Features.LogTemplateErrorsAsDebugMessages, out var logErrorsAsDebug))
|
||||
{
|
||||
return StringUtil.ConvertToBoolean(logErrorsAsDebug, defaultValue: false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class WellKnownTags
|
||||
{
|
||||
public static readonly string Section = "##[section]";
|
||||
|
||||
@@ -26,11 +26,18 @@ namespace GitHub.Runner.Worker.Expressions
|
||||
ArgUtil.NotNull(githubContextData, nameof(githubContextData));
|
||||
var githubContext = githubContextData as DictionaryContextData;
|
||||
ArgUtil.NotNull(githubContext, nameof(githubContext));
|
||||
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out var workspace);
|
||||
|
||||
if (!githubContext.TryGetValue(PipelineTemplateConstants.HostWorkspace, out var workspace))
|
||||
{
|
||||
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out workspace);
|
||||
}
|
||||
ArgUtil.NotNull(workspace, nameof(workspace));
|
||||
|
||||
var workspaceData = workspace as StringContextData;
|
||||
ArgUtil.NotNull(workspaceData, nameof(workspaceData));
|
||||
|
||||
string githubWorkspace = workspaceData.Value;
|
||||
|
||||
bool followSymlink = false;
|
||||
List<string> patterns = new();
|
||||
var firstParameter = true;
|
||||
|
||||
@@ -322,21 +322,9 @@ namespace GitHub.Runner.Worker
|
||||
var equalsIndex = line.IndexOf("=", StringComparison.Ordinal);
|
||||
var heredocIndex = line.IndexOf("<<", StringComparison.Ordinal);
|
||||
|
||||
// Normal style NAME=VALUE
|
||||
if (equalsIndex >= 0 && (heredocIndex < 0 || equalsIndex < heredocIndex))
|
||||
{
|
||||
var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None);
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'. Name must not be empty");
|
||||
}
|
||||
|
||||
key = split[0];
|
||||
output = split[1];
|
||||
}
|
||||
|
||||
// Heredoc style NAME<<EOF
|
||||
else if (heredocIndex >= 0 && (equalsIndex < 0 || heredocIndex < equalsIndex))
|
||||
// Heredoc style NAME<<EOF (where EOF is typically randomly-generated Base64 and may include an '=' character)
|
||||
// see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
if (heredocIndex >= 0 && (equalsIndex < 0 || heredocIndex < equalsIndex))
|
||||
{
|
||||
var split = line.Split(new[] { "<<" }, 2, StringSplitOptions.None);
|
||||
if (string.IsNullOrEmpty(split[0]) || string.IsNullOrEmpty(split[1]))
|
||||
@@ -364,6 +352,18 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
output = endIndex > startIndex ? text.Substring(startIndex, endIndex - startIndex) : string.Empty;
|
||||
}
|
||||
// Normal style NAME=VALUE
|
||||
else if (equalsIndex >= 0 && heredocIndex < 0)
|
||||
{
|
||||
var split = line.Split(new[] { '=' }, 2, StringSplitOptions.None);
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'. Name must not be empty");
|
||||
}
|
||||
|
||||
key = split[0];
|
||||
output = split[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid format '{line}'");
|
||||
|
||||
@@ -310,6 +310,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Mark job as cancelled
|
||||
ExecutionContext.Root.Result = TaskResult.Canceled;
|
||||
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult();
|
||||
step.ExecutionContext.SetGitHubContext("action_status", (ExecutionContext.Root.Result?.ToActionResult() ?? ActionResult.Cancelled).ToString().ToLowerInvariant());
|
||||
|
||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
Data.Image = Data.Image.Substring("docker://".Length);
|
||||
}
|
||||
else if (Data.Image.EndsWith("Dockerfile") || Data.Image.EndsWith("dockerfile"))
|
||||
else if (DockerUtil.IsDockerfile(Data.Image))
|
||||
{
|
||||
// ensure docker file exist
|
||||
dockerFile = Path.Combine(ActionDirectory, Data.Image);
|
||||
@@ -228,6 +228,10 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
|
||||
{
|
||||
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
||||
}
|
||||
|
||||
foreach (var variable in this.Environment)
|
||||
{
|
||||
|
||||
@@ -58,18 +58,31 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var nodeData = data as NodeJSActionExecutionData;
|
||||
|
||||
// With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
(executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode16") ?? false))
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// The user can opt out of this behaviour by setting this variable to true, either setting 'env' in their workflow or as an environment variable on their machine
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
|
||||
var isWorkflowOptOutSet = !string.IsNullOrEmpty(workflowOptOut);
|
||||
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
|
||||
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
|
||||
if (!isOptOut)
|
||||
var repoAction = action as Pipelines.RepositoryPathReference;
|
||||
if (repoAction != null)
|
||||
{
|
||||
nodeData.NodeVersion = "node16";
|
||||
var warningActions = new HashSet<string>();
|
||||
if (executionContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings);
|
||||
}
|
||||
|
||||
string repoActionFullName;
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
|
||||
warningActions.Add(repoActionFullName);
|
||||
executionContext.Global.Variables.Set("Node16ForceActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
nodeData.NodeVersion = "node16";
|
||||
}
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
|
||||
{
|
||||
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
||||
}
|
||||
|
||||
// Resolve the target script.
|
||||
string target = null;
|
||||
@@ -98,20 +102,14 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
}
|
||||
|
||||
#if OS_OSX || OS_WINDOWS
|
||||
if (string.Equals(Data.NodeVersion, "node12", StringComparison.OrdinalIgnoreCase) &&
|
||||
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm64))
|
||||
{
|
||||
#if OS_OSX
|
||||
ExecutionContext.Output($"The node12 is not supported on macOS ARM64 platform. Use node16 instead.");
|
||||
#elif OS_WINDOWS
|
||||
ExecutionContext.Output($"The node12 is not supported on windows ARM64 platform. Use node16 instead.");
|
||||
#endif
|
||||
ExecutionContext.Output($"The node12 is not supported. Use node16 instead.");
|
||||
Data.NodeVersion = "node16";
|
||||
}
|
||||
#endif
|
||||
string forcedNodeVersion = System.Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedActionsNodeVersion);
|
||||
|
||||
string forcedNodeVersion = System.Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedActionsNodeVersion);
|
||||
if (forcedNodeVersion == "node16" && Data.NodeVersion != "node16")
|
||||
{
|
||||
Data.NodeVersion = "node16";
|
||||
@@ -136,29 +134,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Remove environment variable that may cause conflicts with the node within the runner.
|
||||
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
|
||||
|
||||
if (Data.NodeVersion == "node12" && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node12Warning) ?? false))
|
||||
{
|
||||
var repoAction = Action as RepositoryPathReference;
|
||||
var warningActions = new HashSet<string>();
|
||||
if (ExecutionContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings);
|
||||
}
|
||||
|
||||
var repoActionFullName = "";
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
|
||||
warningActions.Add(repoActionFullName);
|
||||
ExecutionContext.Global.Variables.Set("Node12ActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
|
||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,6 +16,7 @@ using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
@@ -34,12 +36,13 @@ namespace GitHub.Runner.Worker
|
||||
public interface IJobExtension : IRunnerService
|
||||
{
|
||||
Task<List<IStep>> InitializeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message);
|
||||
void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc);
|
||||
Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc);
|
||||
}
|
||||
|
||||
public sealed class JobExtension : RunnerService, IJobExtension
|
||||
{
|
||||
private readonly HashSet<string> _existingProcesses = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly List<Task<string>> _connectivityCheckTasks = new();
|
||||
private bool _processCleanup;
|
||||
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
||||
private CancellationTokenSource _diskSpaceCheckToken = new();
|
||||
@@ -175,7 +178,14 @@ namespace GitHub.Runner.Worker
|
||||
context.Debug("Update context data");
|
||||
string _workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
context.SetRunnerContext("workspace", Path.Combine(_workDirectory, trackingConfig.PipelineDirectory));
|
||||
context.SetGitHubContext("workspace", Path.Combine(_workDirectory, trackingConfig.WorkspaceDirectory));
|
||||
|
||||
var githubWorkspace = Path.Combine(_workDirectory, trackingConfig.WorkspaceDirectory);
|
||||
if (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.UseContainerPathForTemplate) ?? false)
|
||||
{
|
||||
// This value is used to translate paths from the container path back to the host path.
|
||||
context.SetGitHubContext("host-workspace", githubWorkspace);
|
||||
}
|
||||
context.SetGitHubContext("workspace", githubWorkspace);
|
||||
|
||||
// Temporary hack for GHES alpha
|
||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||
@@ -421,6 +431,22 @@ namespace GitHub.Runner.Worker
|
||||
_diskSpaceCheckTask = CheckDiskSpaceAsync(context, _diskSpaceCheckToken.Token);
|
||||
}
|
||||
|
||||
// Check server connectivity in background
|
||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
if (systemConnection.Data.TryGetValue("ConnectivityChecks", out var connectivityChecksPayload) &&
|
||||
!string.IsNullOrEmpty(connectivityChecksPayload))
|
||||
{
|
||||
Trace.Info($"Start checking server connectivity.");
|
||||
var checkUrls = StringUtil.ConvertFromJson<List<string>>(connectivityChecksPayload);
|
||||
if (checkUrls?.Count > 0)
|
||||
{
|
||||
foreach (var checkUrl in checkUrls)
|
||||
{
|
||||
_connectivityCheckTasks.Add(CheckConnectivity(checkUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
|
||||
@@ -465,7 +491,7 @@ namespace GitHub.Runner.Worker
|
||||
return reference;
|
||||
}
|
||||
|
||||
public void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc)
|
||||
public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc)
|
||||
{
|
||||
Trace.Entering();
|
||||
ArgUtil.NotNull(jobContext, nameof(jobContext));
|
||||
@@ -642,6 +668,28 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
_diskSpaceCheckToken.Cancel();
|
||||
}
|
||||
|
||||
// Collect server connectivity check result
|
||||
if (_connectivityCheckTasks.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Trace.Info($"Wait for all connectivity checks to finish.");
|
||||
await Task.WhenAll(_connectivityCheckTasks);
|
||||
foreach (var check in _connectivityCheckTasks)
|
||||
{
|
||||
var result = await check;
|
||||
Trace.Info($"Connectivity check result: {result}");
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = result });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Fail to check server connectivity.");
|
||||
Trace.Error(ex);
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = $"Fail to check server connectivity. {ex.Message}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -657,6 +705,37 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> CheckConnectivity(string endpointUrl)
|
||||
{
|
||||
Trace.Info($"Check server connectivity for {endpointUrl}.");
|
||||
string result = string.Empty;
|
||||
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var httpClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
var response = await httpClient.GetAsync(endpointUrl, timeoutTokenSource.Token);
|
||||
result = $"{endpointUrl}: {response.StatusCode}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is OperationCanceledException && timeoutTokenSource.IsCancellationRequested)
|
||||
{
|
||||
Trace.Error($"Request timeout during connectivity check: {ex}");
|
||||
result = $"{endpointUrl}: timeout";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Catch exception during connectivity check: {ex}");
|
||||
result = $"{endpointUrl}: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task CheckDiskSpaceAsync(IExecutionContext context, CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
@@ -228,7 +229,7 @@ namespace GitHub.Runner.Worker
|
||||
finally
|
||||
{
|
||||
Trace.Info("Finalize job.");
|
||||
jobExtension.FinalizeJob(jobContext, message, jobStartTimeUtc);
|
||||
await jobExtension.FinalizeJob(jobContext, message, jobStartTimeUtc);
|
||||
}
|
||||
|
||||
Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}");
|
||||
@@ -268,10 +269,10 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
||||
TaskResult result = jobContext.Complete(taskResult);
|
||||
if (jobContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
// Make sure to clean temp after file upload since they may be pending fileupload still use the TEMP dir.
|
||||
@@ -283,6 +284,13 @@ namespace GitHub.Runner.Worker
|
||||
// Make sure we don't submit secrets as telemetry
|
||||
MaskTelemetrySecrets(jobContext.Global.JobTelemetry);
|
||||
|
||||
// Get environment url
|
||||
string environmentUrl = null;
|
||||
if (jobContext.ActionsEnvironment?.Url is StringToken urlStringToken)
|
||||
{
|
||||
environmentUrl = urlStringToken.Value;
|
||||
}
|
||||
|
||||
Trace.Info($"Raising job completed against run service");
|
||||
var completeJobRetryLimit = 5;
|
||||
var exceptions = new List<Exception>();
|
||||
@@ -290,7 +298,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
try
|
||||
{
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, default);
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, default);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -360,10 +368,10 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
try
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace GitHub.Services.Common
|
||||
public class VssHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with default credentials and request
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with default credentials and request
|
||||
/// settings.
|
||||
/// </summary>
|
||||
public VssHttpMessageHandler()
|
||||
@@ -25,7 +25,7 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with the specified credentials and request
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with the specified credentials and request
|
||||
/// settings.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The credentials which should be used</param>
|
||||
@@ -38,7 +38,7 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with the specified credentials and request
|
||||
/// Initializes a new <c>VssHttpMessageHandler</c> instance with the specified credentials and request
|
||||
/// settings.
|
||||
/// </summary>
|
||||
/// <param name="credentials">The credentials which should be used</param>
|
||||
@@ -211,14 +211,33 @@ namespace GitHub.Services.Common
|
||||
|
||||
traceInfo?.TraceBufferedRequestTime();
|
||||
|
||||
// ConfigureAwait(false) enables the continuation to be run outside any captured
|
||||
// ConfigureAwait(false) enables the continuation to be run outside any captured
|
||||
// SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
|
||||
response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
var tmpResponse = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
if (Settings.AllowAutoRedirectForBroker && tmpResponse.StatusCode == HttpStatusCode.Redirect)
|
||||
{
|
||||
//Dispose of the previous response
|
||||
tmpResponse?.Dispose();
|
||||
|
||||
var location = tmpResponse.Headers.Location;
|
||||
request = new HttpRequestMessage(HttpMethod.Get, location);
|
||||
|
||||
// Reapply the token to new redirected request
|
||||
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
||||
|
||||
// Resend the request
|
||||
response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = tmpResponse;
|
||||
}
|
||||
|
||||
traceInfo?.TraceRequestSendTime();
|
||||
|
||||
// Now buffer the response content if configured to do so. In general we will be buffering
|
||||
// the response content in this location, except in the few cases where the caller has
|
||||
// the response content in this location, except in the few cases where the caller has
|
||||
// specified HttpCompletionOption.ResponseHeadersRead.
|
||||
// Trace content type in case of error
|
||||
await BufferResponseContentAsync(request, response, () => $"[ContentType: {response.Content.GetType().Name}]", tokenSource.Token).ConfigureAwait(false);
|
||||
@@ -235,7 +254,7 @@ namespace GitHub.Services.Common
|
||||
provider.ValidateToken(token, responseWrapper);
|
||||
}
|
||||
|
||||
// Make sure that once we can authenticate with the service that we turn off the
|
||||
// Make sure that once we can authenticate with the service that we turn off the
|
||||
// Expect100Continue behavior to increase performance.
|
||||
this.ExpectContinue = false;
|
||||
succeeded = true;
|
||||
@@ -281,8 +300,8 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
|
||||
// If the user has already tried once but still unauthorized, stop retrying. The main scenario for this condition
|
||||
// is a user typed in a valid credentials for a hosted account but the associated identity does not have
|
||||
// access. We do not want to continually prompt 3 times without telling them the failure reason. In the
|
||||
// is a user typed in a valid credentials for a hosted account but the associated identity does not have
|
||||
// access. We do not want to continually prompt 3 times without telling them the failure reason. In the
|
||||
// next release we should rethink about presenting user the failure and options between retries.
|
||||
IEnumerable<String> headerValues;
|
||||
Boolean hasAuthenticateError =
|
||||
@@ -489,7 +508,7 @@ namespace GitHub.Services.Common
|
||||
httpClientHandler.AllowAutoRedirect = settings.AllowAutoRedirect;
|
||||
httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
|
||||
//Setting httpClientHandler.UseDefaultCredentials to false in .Net Core, clears httpClientHandler.Credentials if
|
||||
//credentials is already set to defaultcredentials. Therefore httpClientHandler.Credentials must be
|
||||
//credentials is already set to defaultcredentials. Therefore httpClientHandler.Credentials must be
|
||||
//set after httpClientHandler.UseDefaultCredentials.
|
||||
httpClientHandler.UseDefaultCredentials = false;
|
||||
httpClientHandler.Credentials = defaultCredentials;
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not HttpClientHandler should follow redirect on outgoing requests.
|
||||
/// Gets or sets a value indicating whether or not HttpClientHandler should follow redirect on outgoing requests.
|
||||
/// </summary>
|
||||
public Boolean AllowAutoRedirect
|
||||
{
|
||||
@@ -111,7 +111,17 @@ namespace GitHub.Services.Common
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not compression should be used on outgoing requests.
|
||||
/// Gets or sets a value indicating whether or not HttpClientHandler should follow redirect on outgoing broker requests
|
||||
/// This is special since this also sends token in the request, where as default AllowAutoRedirect does not
|
||||
/// </summary>
|
||||
public Boolean AllowAutoRedirectForBroker
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not compression should be used on outgoing requests.
|
||||
/// The default value is true.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
public virtual async Task DeleteAgentAsync(
|
||||
int poolId,
|
||||
int agentId,
|
||||
ulong agentId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -243,7 +243,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
public virtual Task<TaskAgent> ReplaceAgentAsync(
|
||||
int poolId,
|
||||
int agentId,
|
||||
ulong agentId,
|
||||
TaskAgent agent,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -786,7 +786,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public virtual Task<TaskAgent> UpdateAgentUpdateStateAsync(
|
||||
int poolId,
|
||||
int agentId,
|
||||
ulong agentId,
|
||||
string currentState,
|
||||
string updateTrace,
|
||||
object userState = null,
|
||||
|
||||
@@ -287,7 +287,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
if (!keys.Contains(property.Key))
|
||||
{
|
||||
m_context.Error(mapping, "Template evaluator error " + $"Required property is missing: {property.Key}");
|
||||
m_context.Error(mapping, $"Required property is missing: {property.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
||||
nonDuplicates.Sort();
|
||||
|
||||
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
||||
m_context.Error(mapping, "Template reader error: " + TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
||||
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
||||
}
|
||||
else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
|
||||
{
|
||||
@@ -289,7 +289,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
||||
{
|
||||
if (!keys.Contains(property.Key))
|
||||
{
|
||||
m_context.Error(mapping, "Template Reader error " + $"Required property is missing: {property.Key}");
|
||||
m_context.Error(mapping, $"Required property is missing: {property.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,5 +82,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
public const String WorkflowRoot = "workflow-root";
|
||||
public const String WorkingDirectory = "working-directory";
|
||||
public const String Workspace = "workspace";
|
||||
public const String HostWorkspace = "host-workspace";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
public AgentRefreshMessage(
|
||||
Int32 agentId,
|
||||
ulong agentId,
|
||||
String targetVersion,
|
||||
TimeSpan? timeout = null)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public Int32 AgentId
|
||||
public ulong AgentId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
[DataContract]
|
||||
public sealed class DiagnosticLogMetadata
|
||||
{
|
||||
public DiagnosticLogMetadata(string agentName, int agentId, int poolId, string phaseName, string fileName, string phaseResult)
|
||||
public DiagnosticLogMetadata(string agentName, ulong agentId, int poolId, string phaseName, string fileName, string phaseResult)
|
||||
{
|
||||
AgentName = agentName;
|
||||
AgentId = agentId;
|
||||
@@ -19,7 +19,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
public string AgentName { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public int AgentId { get; set; }
|
||||
public ulong AgentId { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public int PoolId { get; set; }
|
||||
|
||||
@@ -9,5 +9,8 @@ namespace GitHub.DistributedTask.WebApi
|
||||
|
||||
[EnumMember]
|
||||
ActionCommand = 1,
|
||||
|
||||
[EnumMember]
|
||||
ConnectivityCheck = 2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
[JsonProperty("id")]
|
||||
public Int32 Id
|
||||
public ulong Id
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
public RunnerRefreshMessage(
|
||||
Int32 runnerId,
|
||||
ulong runnerId,
|
||||
String targetVersion,
|
||||
int? timeoutInSeconds = null)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public Int32 RunnerId
|
||||
public ulong RunnerId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
/// Identifier of the agent.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Int32 Id
|
||||
public ulong Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -26,5 +26,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
|
||||
[DataMember(Name = "annotations", EmitDefaultValue = false)]
|
||||
public IList<Annotation> Annotations { get; set; }
|
||||
|
||||
[DataMember(Name = "environmentUrl", EmitDefaultValue = false)]
|
||||
public string EnvironmentUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
Dictionary<String, VariableValue> outputs,
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
@@ -110,7 +111,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
Conclusion = result,
|
||||
Outputs = outputs,
|
||||
StepResults = stepResults,
|
||||
Annotations = jobAnnotations
|
||||
Annotations = jobAnnotations,
|
||||
EnvironmentUrl = environmentUrl,
|
||||
};
|
||||
|
||||
requestUri = new Uri(requestUri, "completejob");
|
||||
|
||||
@@ -86,6 +86,11 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
if (result.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new AccessDeniedException(result.Error);
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to get job message: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using Xunit;
|
||||
@@ -145,6 +145,49 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData("dockerhub/repo", false)]
|
||||
[InlineData("debian:latest", false)]
|
||||
[InlineData("something/dockerfileimage", false)]
|
||||
[InlineData("ghcr.io/docker/dockerfile", true)] // should be false but might break the current workflows
|
||||
[InlineData("Dockerfile", true)]
|
||||
[InlineData("Dockerfile.", true)]
|
||||
[InlineData(".Dockerfile", true)]
|
||||
[InlineData(".Dockerfile.", false)]
|
||||
[InlineData("dockerfile", true)]
|
||||
[InlineData("dockerfile.", true)]
|
||||
[InlineData(".dockerfile", true)]
|
||||
[InlineData(".dockerfile.", false)]
|
||||
[InlineData("Dockerfile.test", true)]
|
||||
[InlineData("test.Dockerfile", true)]
|
||||
[InlineData("docker/dockerfile:latest", false)]
|
||||
[InlineData("/some/path/dockerfile:latest", false)]
|
||||
[InlineData("dockerfile:latest", false)]
|
||||
[InlineData("Dockerfile:latest", false)]
|
||||
[InlineData("dockerfile-latest", false)]
|
||||
[InlineData("Dockerfile-latest", false)]
|
||||
[InlineData("dockerfile.latest", true)]
|
||||
[InlineData("Dockerfile.latest", true)]
|
||||
[InlineData("../dockerfile/dockerfileone", false)]
|
||||
[InlineData("../Dockerfile/dockerfileone", false)]
|
||||
[InlineData("../dockerfile.test", true)]
|
||||
[InlineData("../Dockerfile.test", true)]
|
||||
[InlineData("./dockerfile/image", false)]
|
||||
[InlineData("./Dockerfile/image", false)]
|
||||
[InlineData("example/Dockerfile.test", true)]
|
||||
[InlineData("./example/Dockerfile.test", true)]
|
||||
[InlineData("example/test.dockerfile", true)]
|
||||
[InlineData("./example/test.dockerfile", true)]
|
||||
[InlineData("docker://Dockerfile", false)]
|
||||
[InlineData("docker://ubuntu:latest", false)]
|
||||
public void IsDockerfile(string input, bool expected)
|
||||
{
|
||||
var actual = DockerUtil.IsDockerfile(input);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -180,7 +180,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.200.0"), DownloadUrl = _packageUrl }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -234,7 +234,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -289,7 +289,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -344,7 +344,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = new List<TrimmedPackageMetadata>() { new TrimmedPackageMetadata() } }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -416,7 +416,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -490,7 +490,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -579,7 +579,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -681,7 +681,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -758,7 +758,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, int a, string s, string t) =>
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
|
||||
@@ -186,8 +186,8 @@ namespace GitHub.Runner.Common.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("http_proxy", "127.0.0.1:7777");
|
||||
Environment.SetEnvironmentVariable("https_proxy", "127.0.0.1");
|
||||
Environment.SetEnvironmentVariable("http_proxy", "#fragment");
|
||||
Environment.SetEnvironmentVariable("https_proxy", "#fragment");
|
||||
var proxy = new RunnerWebProxy();
|
||||
|
||||
Assert.Null(proxy.HttpProxyAddress);
|
||||
@@ -206,6 +206,68 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void WebProxyPrependsHTTPforHTTP_PROXY_IfNoProtocol()
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("http_proxy", "127.0.0.1:7777");
|
||||
var proxy = new RunnerWebProxy();
|
||||
|
||||
Assert.Equal("http://127.0.0.1:7777", proxy.HttpProxyAddress);
|
||||
Assert.Null(proxy.HttpProxyUsername);
|
||||
Assert.Null(proxy.HttpProxyPassword);
|
||||
|
||||
Assert.Equal(0, proxy.NoProxyList.Count);
|
||||
|
||||
Environment.SetEnvironmentVariable("http_proxy", "http://127.0.0.1:7777");
|
||||
proxy = new RunnerWebProxy();
|
||||
|
||||
Assert.Equal("http://127.0.0.1:7777", proxy.HttpProxyAddress);
|
||||
Assert.Null(proxy.HttpProxyUsername);
|
||||
Assert.Null(proxy.HttpProxyPassword);
|
||||
|
||||
Assert.Equal(0, proxy.NoProxyList.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CleanProxyEnv();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void WebProxyPrependsHTTPforHTTPS_PROXY_IfNoProtocol()
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("https_proxy", "127.0.0.1:7777");
|
||||
var proxy = new RunnerWebProxy();
|
||||
|
||||
Assert.Equal("http://127.0.0.1:7777", proxy.HttpsProxyAddress);
|
||||
Assert.Null(proxy.HttpProxyUsername);
|
||||
Assert.Null(proxy.HttpProxyPassword);
|
||||
|
||||
Assert.Equal(0, proxy.NoProxyList.Count);
|
||||
|
||||
Environment.SetEnvironmentVariable("https_proxy", "https://127.0.0.1:7777");
|
||||
proxy = new RunnerWebProxy();
|
||||
|
||||
Assert.Equal("https://127.0.0.1:7777", proxy.HttpsProxyAddress); // existing protocol 'https' is not removed
|
||||
Assert.Null(proxy.HttpProxyUsername);
|
||||
Assert.Null(proxy.HttpProxyPassword);
|
||||
|
||||
Assert.Equal(0, proxy.NoProxyList.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CleanProxyEnv();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests
|
||||
{
|
||||
public enum LineEndingType
|
||||
{
|
||||
Native,
|
||||
Linux = 0x__0A,
|
||||
Windows = 0x0D0A
|
||||
}
|
||||
|
||||
public static class TestUtil
|
||||
{
|
||||
private const string Src = "src";
|
||||
@@ -41,5 +52,24 @@ namespace GitHub.Runner.Common.Tests
|
||||
Assert.True(Directory.Exists(testDataDir));
|
||||
return testDataDir;
|
||||
}
|
||||
|
||||
public static void WriteContent(string path, string content, LineEndingType lineEnding = LineEndingType.Native)
|
||||
{
|
||||
WriteContent(path, Enumerable.Repeat(content, 1), lineEnding);
|
||||
}
|
||||
|
||||
public static void WriteContent(string path, IEnumerable<string> content, LineEndingType lineEnding = LineEndingType.Native)
|
||||
{
|
||||
string newline = lineEnding switch
|
||||
{
|
||||
LineEndingType.Linux => "\n",
|
||||
LineEndingType.Windows => "\r\n",
|
||||
_ => Environment.NewLine,
|
||||
};
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1424,6 +1424,74 @@ runs:
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void LoadsNode20ActionDefinition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange.
|
||||
Setup();
|
||||
const string Content = @"
|
||||
# Container action
|
||||
name: 'Hello World'
|
||||
description: 'Greet the world and record the time'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
greeting: # id of input
|
||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||
required: true
|
||||
default: 'Hello'
|
||||
entryPoint: # id of input
|
||||
description: 'optional docker entrypoint overwrite.'
|
||||
required: false
|
||||
outputs:
|
||||
time: # id of output
|
||||
description: 'The time we did the greeting'
|
||||
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'task.js'
|
||||
";
|
||||
Pipelines.ActionStep instance;
|
||||
string directory;
|
||||
CreateAction(yamlContent: Content, instance: out instance, directory: out directory);
|
||||
|
||||
// Act.
|
||||
Definition definition = _actionManager.LoadAction(_ec.Object, instance);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(definition);
|
||||
Assert.Equal(directory, definition.Directory);
|
||||
Assert.NotNull(definition.Data);
|
||||
Assert.NotNull(definition.Data.Inputs); // inputs
|
||||
Dictionary<string, string> inputDefaults = new(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var input in definition.Data.Inputs)
|
||||
{
|
||||
var name = input.Key.AssertString("key").Value;
|
||||
var value = input.Value.AssertScalar("value").ToString();
|
||||
|
||||
_hc.GetTrace().Info($"Default: {name} = {value}");
|
||||
inputDefaults[name] = value;
|
||||
}
|
||||
|
||||
Assert.Equal(2, inputDefaults.Count);
|
||||
Assert.True(inputDefaults.ContainsKey("greeting"));
|
||||
Assert.Equal("Hello", inputDefaults["greeting"]);
|
||||
Assert.True(string.IsNullOrEmpty(inputDefaults["entryPoint"]));
|
||||
Assert.NotNull(definition.Data.Execution); // execution
|
||||
|
||||
Assert.NotNull(definition.Data.Execution as NodeJSActionExecutionData);
|
||||
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
||||
Assert.Equal("node20", (definition.Data.Execution as NodeJSActionExecutionData).NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
|
||||
@@ -459,6 +459,49 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void Load_Node20Action()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Arrange
|
||||
Setup();
|
||||
|
||||
var actionManifest = new ActionManifestManager();
|
||||
actionManifest.Initialize(_hc);
|
||||
|
||||
//Act
|
||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "node20action.yml"));
|
||||
|
||||
//Assert
|
||||
Assert.Equal("Hello World", result.Name);
|
||||
Assert.Equal("Greet the world and record the time", result.Description);
|
||||
Assert.Equal(2, result.Inputs.Count);
|
||||
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||
Assert.Equal(1, result.Deprecated.Count);
|
||||
|
||||
Assert.True(result.Deprecated.ContainsKey("greeting"));
|
||||
result.Deprecated.TryGetValue("greeting", out string value);
|
||||
Assert.Equal("This property has been deprecated", value);
|
||||
|
||||
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||
|
||||
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||
|
||||
Assert.Equal("main.js", nodeAction.Script);
|
||||
Assert.Equal("node20", nodeAction.NodeVersion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -715,7 +758,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
//Assert
|
||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||
Assert.Contains($"Fail to load {action_path}", err.Message);
|
||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
"",
|
||||
"## This is more markdown content",
|
||||
};
|
||||
WriteContent(stepSummaryFile, content);
|
||||
TestUtil.WriteContent(stepSummaryFile, content);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
_jobExecutionContext.Complete();
|
||||
@@ -153,7 +153,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
"",
|
||||
"# GITHUB_TOKEN ghs_verysecuretoken",
|
||||
};
|
||||
WriteContent(stepSummaryFile, content);
|
||||
TestUtil.WriteContent(stepSummaryFile, content);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
@@ -167,21 +167,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
@@ -821,8 +821,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
ec.Complete();
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(1, ec.Global.StepsResult.Count);
|
||||
Assert.Equal(TaskResult.Succeeded, ec.Global.StepsResult.Single().Conclusion);
|
||||
Assert.Equal(0, ec.Global.StepsResult.Count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
420
src/Test/L0/Worker/FileCommandTestBase.cs
Normal file
420
src/Test/L0/Worker/FileCommandTestBase.cs
Normal file
@@ -0,0 +1,420 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public abstract class FileCommandTestBase<T>
|
||||
where T : IFileCommandExtension, new()
|
||||
{
|
||||
|
||||
protected void TestDirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestEmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY=MY VALUE",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal("MY VALUE", _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_KEY=my value",
|
||||
string.Empty,
|
||||
"MY_KEY_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _store.Count);
|
||||
Assert.Equal("my value", _store["MY_KEY"]);
|
||||
Assert.Equal("my second value", _store["MY_KEY_2"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY=",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal(string.Empty, _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY=my value",
|
||||
"MY_KEY_2=",
|
||||
"MY_KEY_3=my third value",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _store.Count);
|
||||
Assert.Equal("my value", _store["MY_KEY"]);
|
||||
Assert.Equal(string.Empty, _store["MY_KEY_2"]);
|
||||
Assert.Equal("my third value", _store["MY_KEY_3"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestSimple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY==abc",
|
||||
"MY_KEY_2=def=ghi",
|
||||
"MY_KEY_3=jkl=",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _store.Count);
|
||||
Assert.Equal("=abc", _store["MY_KEY"]);
|
||||
Assert.Equal("def=ghi", _store["MY_KEY_2"]);
|
||||
Assert.Equal("jkl=", _store["MY_KEY_3"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal($"line one{BREAK}line two{BREAK}line three", _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal(string.Empty, _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_KEY<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_KEY_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _store.Count);
|
||||
Assert.Equal($"hello{BREAK}world", _store["MY_KEY"]);
|
||||
Assert.Equal($"HELLO{BREAK}AGAIN", _store["MY_KEY_2"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY_1<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_KEY_2<<EOF",
|
||||
"hello=two",
|
||||
"EOF",
|
||||
"MY_KEY_3<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
"MY_KEY_4<<EOF",
|
||||
"EOF EOF",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(4, _store.Count);
|
||||
Assert.Equal($"hello{BREAK}{BREAK}three{BREAK}", _store["MY_KEY_1"]);
|
||||
Assert.Equal($"hello=two", _store["MY_KEY_2"]);
|
||||
Assert.Equal($" EOF", _store["MY_KEY_3"]);
|
||||
Assert.Equal($"EOF EOF", _store["MY_KEY_4"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
string eof = validEndMarker;
|
||||
var content = new List<string>
|
||||
{
|
||||
$"MY_KEY_1<<{eof}",
|
||||
$"hello",
|
||||
$"one",
|
||||
$"{eof}",
|
||||
$"MY_KEY_2<<{eof}",
|
||||
$"hello=two",
|
||||
$"{eof}",
|
||||
$"MY_KEY_3<<{eof}",
|
||||
$" {eof}",
|
||||
$"{eof}",
|
||||
$"MY_KEY_4<<{eof}",
|
||||
$"{eof} {eof}",
|
||||
$"{eof}",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(4, _store.Count);
|
||||
Assert.Equal($"hello{BREAK}one", _store["MY_KEY_1"]);
|
||||
Assert.Equal($"hello=two", _store["MY_KEY_2"]);
|
||||
Assert.Equal($" {eof}", _store["MY_KEY_3"]);
|
||||
Assert.Equal($"{eof} {eof}", _store["MY_KEY_4"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
using var hostContext = Setup();
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
|
||||
// Define a hypothetical injectable payload that just happens to contain the '=' character.
|
||||
string contrivedGitHubIssueTitle = "Issue 999: Better handling for the `=` character";
|
||||
|
||||
// The docs recommend using randomly-generated EOF markers.
|
||||
// Here's a randomly-generated base64 EOF marker that just happens to contain an '=' character. ('=' is a padding character in base64.)
|
||||
// see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
string randomizedEOF = "khkIhPxsVA==";
|
||||
var content = new List<string>
|
||||
{
|
||||
// In a real world scenario, "%INJECT%" might instead be something like "${{ github.event.issue.title }}"
|
||||
$"PREFIX_%INJECT%<<{randomizedEOF}".Replace("%INJECT%", contrivedGitHubIssueTitle),
|
||||
"RandomDataThatJustHappensToContainAnEquals=Character",
|
||||
randomizedEOF,
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
var ex = Assert.Throws<Exception>(() => _fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.StartsWith("Invalid format", ex.Message);
|
||||
}
|
||||
|
||||
protected void TestHeredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
string content = "MY_KEY<<EOF line one line two line three EOF";
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
var ex = Assert.Throws<Exception>(() => _fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
string multilineFragment = @"line one
|
||||
line two
|
||||
line three";
|
||||
|
||||
// Note that the final EOF does not appear on it's own line.
|
||||
string content = $"MY_KEY<<EOF {multilineFragment} EOF";
|
||||
TestUtil.WriteContent(stateFile, content);
|
||||
var ex = Assert.Throws<Exception>(() => _fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TestHeredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_KEY<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
TestUtil.WriteContent(stateFile, content, LineEndingType.Linux);
|
||||
_fileCmdExtension.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _store.Count);
|
||||
Assert.Equal($"hello{newline}world", _store["MY_KEY"]);
|
||||
}
|
||||
}
|
||||
|
||||
protected TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(T));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
_store = PostSetup();
|
||||
|
||||
_fileCmdExtension = new T();
|
||||
_fileCmdExtension.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
|
||||
protected abstract IDictionary<string, string> PostSetup();
|
||||
|
||||
protected static readonly string BREAK = Environment.NewLine;
|
||||
|
||||
protected IFileCommandExtension _fileCmdExtension { get; private set; }
|
||||
protected Mock<IExecutionContext> _executionContext { get; private set; }
|
||||
protected List<Tuple<DTWebApi.Issue, string>> _issues { get; private set; }
|
||||
protected IDictionary<string, string> _store { get; private set; }
|
||||
protected string _rootDirectory { get; private set; }
|
||||
protected ITraceWriter _trace { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -30,19 +30,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
[InlineData("node12", "", "", "", "node12")]
|
||||
[InlineData("node12", "true", "", "", "node16")]
|
||||
[InlineData("node12", "true", "", "true", "node12")]
|
||||
[InlineData("node12", "true", "true", "", "node12")]
|
||||
[InlineData("node12", "true", "true", "true", "node12")]
|
||||
[InlineData("node12", "true", "false", "true", "node16")] // workflow overrides env
|
||||
[InlineData("node16", "", "", "", "node16")]
|
||||
[InlineData("node16", "true", "", "", "node16")]
|
||||
[InlineData("node16", "true", "", "true", "node16")]
|
||||
[InlineData("node16", "true", "true", "", "node16")]
|
||||
[InlineData("node16", "true", "true", "true", "node16")]
|
||||
[InlineData("node16", "true", "false", "true", "node16")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string serverFeatureFlag, string workflowOptOut, string machineOptOut, string expectedVersion)
|
||||
[InlineData("node12", "node16")]
|
||||
[InlineData("node16", "node16")]
|
||||
[InlineData("node20", "node20")]
|
||||
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -52,24 +43,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
|
||||
// Server Feature Flag
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
if (!string.IsNullOrEmpty(serverFeatureFlag))
|
||||
{
|
||||
variables["DistributedTask.ForceGithubJavascriptActionsToNode16"] = serverFeatureFlag;
|
||||
}
|
||||
Variables serverVariables = new(hc, variables);
|
||||
|
||||
// Workflow opt-out
|
||||
var workflowVariables = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(workflowOptOut))
|
||||
{
|
||||
workflowVariables[Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion] = workflowOptOut;
|
||||
}
|
||||
|
||||
// Machine opt-out
|
||||
if (!string.IsNullOrEmpty(machineOptOut))
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, machineOptOut);
|
||||
}
|
||||
|
||||
_ec.Setup(x => x.Global).Returns(new GlobalContext()
|
||||
{
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Worker;
|
||||
using Moq;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Worker;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
@@ -105,6 +105,18 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
github["repository"] = new Pipelines.ContextData.StringContextData("actions/runner");
|
||||
github["secret_source"] = new Pipelines.ContextData.StringContextData("Actions");
|
||||
_message.ContextData.Add("github", github);
|
||||
_message.Resources.Endpoints.Add(new ServiceEndpoint()
|
||||
{
|
||||
Name = WellKnownServiceEndpointNames.SystemVssConnection,
|
||||
Url = new Uri("https://pipelines.actions.githubusercontent.com"),
|
||||
Authorization = new EndpointAuthorization()
|
||||
{
|
||||
Scheme = "Test",
|
||||
Parameters = {
|
||||
{"AccessToken", "token"}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
hc.SetSingleton(_actionManager.Object);
|
||||
hc.SetSingleton(_config.Object);
|
||||
@@ -231,7 +243,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void UploadDiganosticLogIfEnvironmentVariableSet()
|
||||
public async Task UploadDiganosticLogIfEnvironmentVariableSet()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -244,7 +256,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_jobEc.Initialize(hc);
|
||||
_jobEc.InitializeJob(_message, _tokenSource.Token);
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
_diagnosticLogManager.Verify(x =>
|
||||
x.UploadDiagnosticLogs(
|
||||
@@ -259,7 +271,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void DontUploadDiagnosticLogIfEnvironmentVariableFalse()
|
||||
public async Task DontUploadDiagnosticLogIfEnvironmentVariableFalse()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -272,7 +284,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_jobEc.Initialize(hc);
|
||||
_jobEc.InitializeJob(_message, _tokenSource.Token);
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
_diagnosticLogManager.Verify(x =>
|
||||
x.UploadDiagnosticLogs(
|
||||
@@ -287,14 +299,14 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void DontUploadDiagnosticLogIfEnvironmentVariableMissing()
|
||||
public async Task DontUploadDiagnosticLogIfEnvironmentVariableMissing()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
var jobExtension = new JobExtension();
|
||||
jobExtension.Initialize(hc);
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
_diagnosticLogManager.Verify(x =>
|
||||
x.UploadDiagnosticLogs(
|
||||
@@ -309,7 +321,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EnsureFinalizeJobRunsIfMessageHasNoEnvironmentUrl()
|
||||
public async Task EnsureFinalizeJobRunsIfMessageHasNoEnvironmentUrl()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -322,7 +334,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_jobEc.Initialize(hc);
|
||||
_jobEc.InitializeJob(_message, _tokenSource.Token);
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
|
||||
}
|
||||
@@ -331,7 +343,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EnsureFinalizeJobHandlesNullEnvironmentUrl()
|
||||
public async Task EnsureFinalizeJobHandlesNullEnvironmentUrl()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -347,7 +359,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_jobEc.Initialize(hc);
|
||||
_jobEc.InitializeJob(_message, _tokenSource.Token);
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
|
||||
}
|
||||
@@ -356,7 +368,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EnsureFinalizeJobHandlesNullEnvironment()
|
||||
public async Task EnsureFinalizeJobHandlesNullEnvironment()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -369,7 +381,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_jobEc.Initialize(hc);
|
||||
_jobEc.InitializeJob(_message, _tokenSource.Token);
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
|
||||
}
|
||||
@@ -397,7 +409,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
|
||||
var hookStart = result.First() as JobExtensionRunner;
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(Constants.Hooks.JobStartedStepName, hookStart.DisplayName);
|
||||
Assert.Equal(Constants.Hooks.JobCompletedStepName, (_jobEc.PostJobSteps.Last() as JobExtensionRunner).DisplayName);
|
||||
@@ -410,7 +422,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EnsureNoPreAndPostHookSteps()
|
||||
public async Task EnsureNoPreAndPostHookSteps()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -425,7 +437,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
|
||||
var x = _jobEc.JobSteps;
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
await jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
|
||||
Assert.Equal(0, _jobEc.PostJobSteps.Count);
|
||||
|
||||
@@ -99,7 +99,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
timeline,
|
||||
jobId,
|
||||
testName,
|
||||
testName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null,
|
||||
testName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null,
|
||||
new ActionsEnvironmentReference("staging"),
|
||||
messageType: messageType);
|
||||
message.Variables[Constants.Variables.System.Culture] = "en-US";
|
||||
message.Resources.Endpoints.Add(new ServiceEndpoint()
|
||||
|
||||
@@ -1,44 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class SaveStateFileCommandL0
|
||||
public sealed class SaveStateFileCommandL0 : FileCommandTestBase<SaveStateFileCommand>
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private string _rootDirectory;
|
||||
private SaveStateFileCommand _saveStateFileCommand;
|
||||
private Dictionary<string, string> _intraActionState;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
protected override IDictionary<string, string> PostSetup()
|
||||
{
|
||||
var intraActionState = new Dictionary<string, string>();
|
||||
_executionContext.Setup(x => x.IntraActionState).Returns(intraActionState);
|
||||
return intraActionState;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _intraActionState.Count);
|
||||
}
|
||||
base.TestDirectoryNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -46,13 +29,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_NotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _intraActionState.Count);
|
||||
}
|
||||
base.TestNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,15 +37,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _intraActionState.Count);
|
||||
}
|
||||
base.TestEmptyFile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,19 +45,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE=MY VALUE",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal("MY VALUE", _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestSimple();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -96,24 +53,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_STATE=my value",
|
||||
string.Empty,
|
||||
"MY_STATE_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _intraActionState.Count);
|
||||
Assert.Equal("my value", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal("my second value", _intraActionState["MY_STATE_2"]);
|
||||
}
|
||||
base.TestSimple_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,19 +61,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal(string.Empty, _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestSimple_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -141,23 +69,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE=my value",
|
||||
"MY_STATE_2=",
|
||||
"MY_STATE_3=my third value",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _intraActionState.Count);
|
||||
Assert.Equal("my value", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal(string.Empty, _intraActionState["MY_STATE_2"]);
|
||||
Assert.Equal("my third value", _intraActionState["MY_STATE_3"]);
|
||||
}
|
||||
base.TestSimple_MultipleValues();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -165,23 +77,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Simple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE==abc",
|
||||
"MY_STATE_2=def=ghi",
|
||||
"MY_STATE_3=jkl=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _intraActionState.Count);
|
||||
Assert.Equal("=abc", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal("def=ghi", _intraActionState["MY_STATE_2"]);
|
||||
Assert.Equal("jkl=", _intraActionState["MY_STATE_3"]);
|
||||
}
|
||||
base.TestSimple_SpecialCharacters();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -189,23 +85,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal($"line one{Environment.NewLine}line two{Environment.NewLine}line three", _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestHeredoc();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -213,20 +93,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal(string.Empty, _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestHeredoc_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -234,73 +101,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_STATE<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_STATE_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _intraActionState.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}world", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal($"HELLO{Environment.NewLine}AGAIN", _intraActionState["MY_STATE_2"]);
|
||||
}
|
||||
base.TestHeredoc_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_SpecialCharacters()
|
||||
public void SaveStateFileCommand_Heredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<=EOF",
|
||||
"hello",
|
||||
"one",
|
||||
"=EOF",
|
||||
"MY_STATE_2<<<EOF",
|
||||
"hello",
|
||||
"two",
|
||||
"<EOF",
|
||||
"MY_STATE_3<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_STATE_4<<EOF",
|
||||
"hello=four",
|
||||
"EOF",
|
||||
"MY_STATE_5<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(5, _intraActionState.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}one", _intraActionState["MY_STATE"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}two", _intraActionState["MY_STATE_2"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}{Environment.NewLine}three{Environment.NewLine}", _intraActionState["MY_STATE_3"]);
|
||||
Assert.Equal($"hello=four", _intraActionState["MY_STATE_4"]);
|
||||
Assert.Equal($" EOF", _intraActionState["MY_STATE_5"]);
|
||||
}
|
||||
base.TestHeredoc_EdgeCases();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
// All of the following are not only valid, but quite plausible end markers.
|
||||
// Most are derived straight from the example at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
#pragma warning disable format
|
||||
[InlineData("=EOF")][InlineData("==EOF")][InlineData("EO=F")][InlineData("EO==F")][InlineData("EOF=")][InlineData("EOF==")]
|
||||
[InlineData("<EOF")][InlineData("<<EOF")][InlineData("EO<F")][InlineData("EO<<F")][InlineData("EOF<")][InlineData("EOF<<")]
|
||||
[InlineData("+EOF")][InlineData("++EOF")][InlineData("EO+F")][InlineData("EO++F")][InlineData("EOF+")][InlineData("EOF++")]
|
||||
[InlineData("/EOF")][InlineData("//EOF")][InlineData("EO/F")][InlineData("EO//F")][InlineData("EOF/")][InlineData("EOF//")]
|
||||
#pragma warning restore format
|
||||
[InlineData("<<//++==")]
|
||||
[InlineData("contrivedBase64==")]
|
||||
[InlineData("khkIhPxsVA==")]
|
||||
[InlineData("D+Y8zE/EOw==")]
|
||||
[InlineData("wuOWG4S6FQ==")]
|
||||
[InlineData("7wigCJ//iw==")]
|
||||
[InlineData("uifTuYTs8K4=")]
|
||||
[InlineData("M7N2ITg/04c=")]
|
||||
[InlineData("Xhh+qp+Y6iM=")]
|
||||
[InlineData("5tdblQajc/b+EGBZXo0w")]
|
||||
[InlineData("jk/UMjIx/N0eVcQYOUfw")]
|
||||
[InlineData("/n5lsw73Cwl35Hfuscdz")]
|
||||
[InlineData("ZvnAEW+9O0tXp3Fmb3Oh")]
|
||||
public void SaveStateFileCommand_Heredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
base.TestHeredoc_EndMarkerVariations(validEndMarker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
base.TestHeredoc_EqualBeforeMultilineIndicator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -308,21 +154,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLine();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -330,21 +162,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
@"line one
|
||||
line two
|
||||
line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLineMultipleLines();
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
@@ -353,90 +171,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SaveStateFileCommand_Heredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_STATE<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, newline: newline);
|
||||
_saveStateFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _intraActionState.Count);
|
||||
Assert.Equal($"hello{newline}world", _intraActionState["MY_STATE"]);
|
||||
}
|
||||
base.TestHeredoc_PreservesNewline();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
_intraActionState = new Dictionary<string, string>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(SaveStateFileCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
_executionContext.Setup(x => x.IntraActionState)
|
||||
.Returns(_intraActionState);
|
||||
|
||||
// SaveStateFileCommand
|
||||
_saveStateFileCommand = new SaveStateFileCommand();
|
||||
_saveStateFileCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class SetEnvFileCommandL0
|
||||
public sealed class SetEnvFileCommandL0 : FileCommandTestBase<SetEnvFileCommand>
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private string _rootDirectory;
|
||||
private SetEnvFileCommand _setEnvFileCommand;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
protected override IDictionary<string, string> PostSetup()
|
||||
{
|
||||
return _executionContext.Object.Global.EnvironmentVariables;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestDirectoryNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,13 +27,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_NotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -59,15 +35,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
}
|
||||
base.TestEmptyFile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -75,19 +43,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV=MY VALUE",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("MY VALUE", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestSimple();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -95,24 +51,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_ENV=my value",
|
||||
string.Empty,
|
||||
"MY_ENV_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal("my second value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
}
|
||||
base.TestSimple_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -120,19 +59,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV=",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestSimple_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -140,23 +67,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV=my value",
|
||||
"MY_ENV_2=",
|
||||
"MY_ENV_3=my third value",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
Assert.Equal("my third value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]);
|
||||
}
|
||||
base.TestSimple_MultipleValues();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -164,23 +75,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Simple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV==abc",
|
||||
"MY_ENV_2=def=ghi",
|
||||
"MY_ENV_3=jkl=",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal("=abc", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal("def=ghi", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
Assert.Equal("jkl=", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]);
|
||||
}
|
||||
base.TestSimple_SpecialCharacters();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -188,23 +83,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"line one{Environment.NewLine}line two{Environment.NewLine}line three", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestHeredoc();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -212,20 +91,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestHeredoc_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -233,73 +99,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_ENV<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_ENV_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}world", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal($"HELLO{Environment.NewLine}AGAIN", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
}
|
||||
base.TestHeredoc_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_SpecialCharacters()
|
||||
public void SetEnvFileCommand_Heredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<=EOF",
|
||||
"hello",
|
||||
"one",
|
||||
"=EOF",
|
||||
"MY_ENV_2<<<EOF",
|
||||
"hello",
|
||||
"two",
|
||||
"<EOF",
|
||||
"MY_ENV_3<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_ENV_4<<EOF",
|
||||
"hello=four",
|
||||
"EOF",
|
||||
"MY_ENV_5<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(5, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}one", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}two", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}{Environment.NewLine}three{Environment.NewLine}", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]);
|
||||
Assert.Equal($"hello=four", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_4"]);
|
||||
Assert.Equal($" EOF", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_5"]);
|
||||
}
|
||||
base.TestHeredoc_EdgeCases();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
// All of the following are not only valid, but quite plausible end markers.
|
||||
// Most are derived straight from the example at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
#pragma warning disable format
|
||||
[InlineData("=EOF")][InlineData("==EOF")][InlineData("EO=F")][InlineData("EO==F")][InlineData("EOF=")][InlineData("EOF==")]
|
||||
[InlineData("<EOF")][InlineData("<<EOF")][InlineData("EO<F")][InlineData("EO<<F")][InlineData("EOF<")][InlineData("EOF<<")]
|
||||
[InlineData("+EOF")][InlineData("++EOF")][InlineData("EO+F")][InlineData("EO++F")][InlineData("EOF+")][InlineData("EOF++")]
|
||||
[InlineData("/EOF")][InlineData("//EOF")][InlineData("EO/F")][InlineData("EO//F")][InlineData("EOF/")][InlineData("EOF//")]
|
||||
#pragma warning restore format
|
||||
[InlineData("<<//++==")]
|
||||
[InlineData("contrivedBase64==")]
|
||||
[InlineData("khkIhPxsVA==")]
|
||||
[InlineData("D+Y8zE/EOw==")]
|
||||
[InlineData("wuOWG4S6FQ==")]
|
||||
[InlineData("7wigCJ//iw==")]
|
||||
[InlineData("uifTuYTs8K4=")]
|
||||
[InlineData("M7N2ITg/04c=")]
|
||||
[InlineData("Xhh+qp+Y6iM=")]
|
||||
[InlineData("5tdblQajc/b+EGBZXo0w")]
|
||||
[InlineData("jk/UMjIx/N0eVcQYOUfw")]
|
||||
[InlineData("/n5lsw73Cwl35Hfuscdz")]
|
||||
[InlineData("ZvnAEW+9O0tXp3Fmb3Oh")]
|
||||
public void SetEnvFileCommand_Heredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
base.TestHeredoc_EndMarkerVariations(validEndMarker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
base.TestHeredoc_EqualBeforeMultilineIndicator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -307,43 +152,15 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLine();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_MissingNewLineMultipleLinesEnv()
|
||||
public void SetEnvFileCommand_Heredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
@"line one
|
||||
line two
|
||||
line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLineMultipleLines();
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
@@ -352,87 +169,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetEnvFileCommand_Heredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var envFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_ENV<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(envFile, content, newline: newline);
|
||||
_setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count);
|
||||
Assert.Equal($"hello{newline}world", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]);
|
||||
}
|
||||
base.TestHeredoc_PreservesNewline();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(SetEnvFileCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
// SetEnvFileCommand
|
||||
_setEnvFileCommand = new SetEnvFileCommand();
|
||||
_setEnvFileCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class SetOutputFileCommandL0
|
||||
public sealed class SetOutputFileCommandL0 : FileCommandTestBase<SetOutputFileCommand>
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private Dictionary<string, string> _outputs;
|
||||
private string _rootDirectory;
|
||||
private SetOutputFileCommand _setOutputFileCommand;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
protected override IDictionary<string, string> PostSetup()
|
||||
{
|
||||
var outputs = new Dictionary<string, string>();
|
||||
var reference = string.Empty;
|
||||
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
||||
.Callback((string name, string value, out string reference) =>
|
||||
{
|
||||
reference = value;
|
||||
outputs[name] = value;
|
||||
});
|
||||
|
||||
return outputs;
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _outputs.Count);
|
||||
}
|
||||
base.TestDirectoryNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -46,13 +38,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_NotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _outputs.Count);
|
||||
}
|
||||
base.TestNotFound();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,15 +46,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
var content = new List<string>();
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(0, _outputs.Count);
|
||||
}
|
||||
base.TestEmptyFile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,19 +54,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT=MY VALUE",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal("MY VALUE", _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestSimple();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -96,24 +62,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_OUTPUT=my value",
|
||||
string.Empty,
|
||||
"MY_OUTPUT_2=my second value",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _outputs.Count);
|
||||
Assert.Equal("my value", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal("my second value", _outputs["MY_OUTPUT_2"]);
|
||||
}
|
||||
base.TestSimple_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,19 +70,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple-empty-value");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal(string.Empty, _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestSimple_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -141,23 +78,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_MultipleValues()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT=my value",
|
||||
"MY_OUTPUT_2=",
|
||||
"MY_OUTPUT_3=my third value",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _outputs.Count);
|
||||
Assert.Equal("my value", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal(string.Empty, _outputs["MY_OUTPUT_2"]);
|
||||
Assert.Equal("my third value", _outputs["MY_OUTPUT_3"]);
|
||||
}
|
||||
base.TestSimple_MultipleValues();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -165,23 +86,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Simple_SpecialCharacters()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT==abc",
|
||||
"MY_OUTPUT_2=def=ghi",
|
||||
"MY_OUTPUT_3=jkl=",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(3, _outputs.Count);
|
||||
Assert.Equal("=abc", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal("def=ghi", _outputs["MY_OUTPUT_2"]);
|
||||
Assert.Equal("jkl=", _outputs["MY_OUTPUT_3"]);
|
||||
}
|
||||
base.TestSimple_SpecialCharacters();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -189,23 +94,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal($"line one{Environment.NewLine}line two{Environment.NewLine}line three", _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestHeredoc();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -213,20 +102,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_EmptyValue()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal(string.Empty, _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestHeredoc_EmptyValue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -234,73 +110,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_SkipEmptyLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
string.Empty,
|
||||
"MY_OUTPUT<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
"MY_OUTPUT_2<<EOF",
|
||||
"HELLO",
|
||||
"AGAIN",
|
||||
"EOF",
|
||||
string.Empty,
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(2, _outputs.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}world", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal($"HELLO{Environment.NewLine}AGAIN", _outputs["MY_OUTPUT_2"]);
|
||||
}
|
||||
base.TestHeredoc_SkipEmptyLines();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_SpecialCharacters()
|
||||
public void SetOutputFileCommand_Heredoc_EdgeCases()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<=EOF",
|
||||
"hello",
|
||||
"one",
|
||||
"=EOF",
|
||||
"MY_OUTPUT_2<<<EOF",
|
||||
"hello",
|
||||
"two",
|
||||
"<EOF",
|
||||
"MY_OUTPUT_3<<EOF",
|
||||
"hello",
|
||||
string.Empty,
|
||||
"three",
|
||||
string.Empty,
|
||||
"EOF",
|
||||
"MY_OUTPUT_4<<EOF",
|
||||
"hello=four",
|
||||
"EOF",
|
||||
"MY_OUTPUT_5<<EOF",
|
||||
" EOF",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(5, _outputs.Count);
|
||||
Assert.Equal($"hello{Environment.NewLine}one", _outputs["MY_OUTPUT"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}two", _outputs["MY_OUTPUT_2"]);
|
||||
Assert.Equal($"hello{Environment.NewLine}{Environment.NewLine}three{Environment.NewLine}", _outputs["MY_OUTPUT_3"]);
|
||||
Assert.Equal($"hello=four", _outputs["MY_OUTPUT_4"]);
|
||||
Assert.Equal($" EOF", _outputs["MY_OUTPUT_5"]);
|
||||
}
|
||||
base.TestHeredoc_EdgeCases();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
// All of the following are not only valid, but quite plausible end markers.
|
||||
// Most are derived straight from the example at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
#pragma warning disable format
|
||||
[InlineData("=EOF")][InlineData("==EOF")][InlineData("EO=F")][InlineData("EO==F")][InlineData("EOF=")][InlineData("EOF==")]
|
||||
[InlineData("<EOF")][InlineData("<<EOF")][InlineData("EO<F")][InlineData("EO<<F")][InlineData("EOF<")][InlineData("EOF<<")]
|
||||
[InlineData("+EOF")][InlineData("++EOF")][InlineData("EO+F")][InlineData("EO++F")][InlineData("EOF+")][InlineData("EOF++")]
|
||||
[InlineData("/EOF")][InlineData("//EOF")][InlineData("EO/F")][InlineData("EO//F")][InlineData("EOF/")][InlineData("EOF//")]
|
||||
#pragma warning restore format
|
||||
[InlineData("<<//++==")]
|
||||
[InlineData("contrivedBase64==")]
|
||||
[InlineData("khkIhPxsVA==")]
|
||||
[InlineData("D+Y8zE/EOw==")]
|
||||
[InlineData("wuOWG4S6FQ==")]
|
||||
[InlineData("7wigCJ//iw==")]
|
||||
[InlineData("uifTuYTs8K4=")]
|
||||
[InlineData("M7N2ITg/04c=")]
|
||||
[InlineData("Xhh+qp+Y6iM=")]
|
||||
[InlineData("5tdblQajc/b+EGBZXo0w")]
|
||||
[InlineData("jk/UMjIx/N0eVcQYOUfw")]
|
||||
[InlineData("/n5lsw73Cwl35Hfuscdz")]
|
||||
[InlineData("ZvnAEW+9O0tXp3Fmb3Oh")]
|
||||
public void SetOutputFileCommand_Heredoc_EndMarkerVariations(string validEndMarker)
|
||||
{
|
||||
base.TestHeredoc_EndMarkerVariations(validEndMarker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_EqualBeforeMultilineIndicator()
|
||||
{
|
||||
base.TestHeredoc_EqualBeforeMultilineIndicator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -308,21 +163,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_MissingNewLine()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"line one",
|
||||
"line two",
|
||||
"line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("Matching delimiter not found", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLine();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -330,21 +171,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_MissingNewLineMultipleLines()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
@"line one
|
||||
line two
|
||||
line three",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, " ");
|
||||
var ex = Assert.Throws<Exception>(() => _setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null));
|
||||
Assert.Contains("EOF marker missing new line", ex.Message);
|
||||
}
|
||||
base.TestHeredoc_MissingNewLineMultipleLines();
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
@@ -353,96 +180,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Category", "Worker")]
|
||||
public void SetOutputFileCommand_Heredoc_PreservesNewline()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var newline = "\n";
|
||||
var stateFile = Path.Combine(_rootDirectory, "heredoc");
|
||||
var content = new List<string>
|
||||
{
|
||||
"MY_OUTPUT<<EOF",
|
||||
"hello",
|
||||
"world",
|
||||
"EOF",
|
||||
};
|
||||
WriteContent(stateFile, content, newline: newline);
|
||||
_setOutputFileCommand.ProcessCommand(_executionContext.Object, stateFile, null);
|
||||
Assert.Equal(0, _issues.Count);
|
||||
Assert.Equal(1, _outputs.Count);
|
||||
Assert.Equal($"hello{newline}world", _outputs["MY_OUTPUT"]);
|
||||
}
|
||||
base.TestHeredoc_PreservesNewline();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> content,
|
||||
string newline = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newline))
|
||||
{
|
||||
newline = Environment.NewLine;
|
||||
}
|
||||
|
||||
var encoding = new UTF8Encoding(true); // Emit BOM
|
||||
var contentStr = string.Join(newline, content);
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
_outputs = new Dictionary<string, string>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(SetOutputFileCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||
{
|
||||
var resolvedMessage = issue.Message;
|
||||
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||
{
|
||||
resolvedMessage = logOptions.LogMessageOverride;
|
||||
}
|
||||
_issues.Add(new(issue, resolvedMessage));
|
||||
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
var reference = string.Empty;
|
||||
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
||||
.Callback((string name, string value, out string reference) =>
|
||||
{
|
||||
reference = value;
|
||||
_outputs[name] = value;
|
||||
});
|
||||
|
||||
// SetOutputFileCommand
|
||||
_setOutputFileCommand = new SetOutputFileCommand();
|
||||
_setOutputFileCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,33 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task DetermineNode20RuntimeVersionInAlpineContainerAsync()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var sh = new ContainerStepHost();
|
||||
sh.Initialize(hc);
|
||||
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||
|
||||
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||
{
|
||||
output.Add("alpine");
|
||||
})
|
||||
.ReturnsAsync(0);
|
||||
|
||||
// Act.
|
||||
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node20");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal("node20_alpine", nodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -108,6 +135,33 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal("node16", nodeVersion);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task DetermineNode20RuntimeVersionInUnknowContainerAsync()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var sh = new ContainerStepHost();
|
||||
sh.Initialize(hc);
|
||||
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||
|
||||
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||
{
|
||||
output.Add("github");
|
||||
})
|
||||
.ReturnsAsync(0);
|
||||
|
||||
// Act.
|
||||
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node20");
|
||||
|
||||
// Assert.
|
||||
Assert.Equal("node20", nodeVersion);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
20
src/Test/TestData/node20action.yml
Normal file
20
src/Test/TestData/node20action.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'Hello World'
|
||||
description: 'Greet the world and record the time'
|
||||
author: 'Test Corporation'
|
||||
inputs:
|
||||
greeting: # id of input
|
||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||
required: true
|
||||
default: 'Hello'
|
||||
deprecationMessage: 'This property has been deprecated'
|
||||
entryPoint: # id of input
|
||||
description: 'optional docker entrypoint overwrite.'
|
||||
required: false
|
||||
outputs:
|
||||
time: # id of output
|
||||
description: 'The time we did the greeting'
|
||||
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'main.js'
|
||||
@@ -22,7 +22,7 @@ DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
PACKAGE_TRIMS_DIR="$SCRIPT_DIR/../_package_trims"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="6.0.405"
|
||||
DOTNETSDK_VERSION="6.0.412"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.405"
|
||||
"version": "6.0.412"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
2.304.0
|
||||
2.308.0
|
||||
|
||||
Reference in New Issue
Block a user