mirror of
https://github.com/actions/runner.git
synced 2025-12-12 05:37:01 +00:00
Compare commits
17 Commits
users/tihu
...
v2.168.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fc0686dc2 | ||
|
|
ab001a7004 | ||
|
|
178a618e01 | ||
|
|
dfaf6e06ee | ||
|
|
b0a71481f0 | ||
|
|
88875ca1b0 | ||
|
|
a5eb8cb5c4 | ||
|
|
41f4ca3414 | ||
|
|
aa9f5bf070 | ||
|
|
2d6042421f | ||
|
|
c8890d0f3f | ||
|
|
53fb6297cb | ||
|
|
f9b5d626c5 | ||
|
|
d34afb54b1 | ||
|
|
e291ebc58a | ||
|
|
6bec1e3bb8 | ||
|
|
0cba42590f |
62
docs/adrs/0274-step-outcome-and-conclusion.md
Normal file
62
docs/adrs/0274-step-outcome-and-conclusion.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# ADR 0274: Step outcome and conclusion
|
||||||
|
|
||||||
|
**Date**: 2020-01-13
|
||||||
|
|
||||||
|
**Status**: Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
This ADR proposes adding `steps.<id>.outcome` and `steps.<id>.conclusion` to the steps context.
|
||||||
|
|
||||||
|
This allows downstream a step to run based on whether a previous step succeeded or failed.
|
||||||
|
|
||||||
|
Reminder, currently the steps contains `steps.<id>.outputs`.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
For steps that have completed, populate `steps.<id>.outcome` and `steps.<id>.conclusion` with one of the following values:
|
||||||
|
|
||||||
|
- `success`
|
||||||
|
- `failure`
|
||||||
|
- `cancelled`
|
||||||
|
- `skipped`
|
||||||
|
|
||||||
|
When a continue-on-error step fails, the outcome will be `failure` even though the final conclusion is `success`.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- id: experimental
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./build.sh experimental
|
||||||
|
|
||||||
|
- if: ${{ steps.experimental.outcome == 'success' }}
|
||||||
|
run: ./publish.sh experimental
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminology
|
||||||
|
|
||||||
|
The runs API uses the term `conclusion`.
|
||||||
|
|
||||||
|
Therefore we use a different term `outcome` for the value prior to continue-on-error.
|
||||||
|
|
||||||
|
The following is a snippet from the runs API response payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "Set up job",
|
||||||
|
"status": "completed",
|
||||||
|
"conclusion": "success",
|
||||||
|
"number": 1,
|
||||||
|
"started_at": "2020-01-09T11:06:16.000-05:00",
|
||||||
|
"completed_at": "2020-01-09T11:06:18.000-05:00"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Update runner
|
||||||
|
- Update [docs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#steps-context)
|
||||||
35
docs/adrs/0354-runner-machine-info.md
Normal file
35
docs/adrs/0354-runner-machine-info.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# ADR 354: Expose runner machine info
|
||||||
|
|
||||||
|
**Date**: 2020-03-02
|
||||||
|
|
||||||
|
**Status**: Pending
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Provide a mechanism in the runner to include extra information in `Set up job` step's log.
|
||||||
|
Ex: Include OS/Software info from Hosted image.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
The runner will look for a file `.setup_info` under the runner's root directory, The file can be a JSON with a simple schema.
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"group": "OS Detail",
|
||||||
|
"detail": "........"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Software Detail",
|
||||||
|
"detail": "........"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
The runner will use `##[group]` and `##[endgroup]` to fold all detail info into an expandable group.
|
||||||
|
|
||||||
|
Both [virtual-environments](https://github.com/actions/virtual-environments) and self-hosted runners can use this mechanism to add extra logging info to the `Set up job` step's log.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
1. Change the runner to best effort read/parse `.extra_setup_info` file under runner root directory.
|
||||||
|
2. [virtual-environments](https://github.com/actions/virtual-environments) generate the file during image generation.
|
||||||
|
3. Change MMS provisioner to properly copy the file to runner root directory at runtime.
|
||||||
@@ -44,7 +44,7 @@ Sample developer flow:
|
|||||||
```bash
|
```bash
|
||||||
git clone https://github.com/actions/runner
|
git clone https://github.com/actions/runner
|
||||||
cd ./src
|
cd ./src
|
||||||
./dev.(sh/cmd) layout # the runner that build from source is in {root}/_layout
|
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
||||||
<make code changes>
|
<make code changes>
|
||||||
./dev.(sh/cmd) build # {root}/_layout will get updated
|
./dev.(sh/cmd) build # {root}/_layout will get updated
|
||||||
./dev.(sh/cmd) test # run all unit tests before git commit/push
|
./dev.(sh/cmd) test # run all unit tests before git commit/push
|
||||||
|
|||||||
61
docs/design/auth.md
Normal file
61
docs/design/auth.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Runner Authentication and Authorization
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
- Support runner installs in untrusted domains.
|
||||||
|
- The account that configures or runs the runner process is not relevant for accessing GitHub resources.
|
||||||
|
- Accessing GitHub resources is done with a per-job token which expires when job completes.
|
||||||
|
- The token is granted to trusted parts of the system including the runner, actions and script steps specified by the workflow author as trusted.
|
||||||
|
- All OAuth tokens that come from the Token Service that the runner uses to access Actions Service resources are the same. It's just the scope and expiration of the token that may vary.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuring a self-hosted runner is [covered here in the documentation](https://help.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners).
|
||||||
|
|
||||||
|
Configuration is done with the user being authenticated via a time-limited, GitHub runner registration token.
|
||||||
|
|
||||||
|
*Your credentials are never used for registering the runner with the service.*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
During configuration, an RSA public/private key pair is created, the private key is stored in file on disk. On Windows, the content is protected with DPAPI (machine level encrypted - runner only valid on that machine) and on Linux/OSX with `chmod` permissions.
|
||||||
|
|
||||||
|
Using your credentials, the runner is registered with the service by sending the public key to the service which adds that runner to the pool and stores the public key, the Token Service will generate a `clientId` associated with the public key.
|
||||||
|
|
||||||
|
## Start and Listen
|
||||||
|
|
||||||
|
After configuring the runner, the runner can be started interactively (`./run.cmd` or `./run.sh`) or as a service.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
On start, the runner listener process loads the RSA private key (on Windows decrypting with machine key DPAPI), and asks the Token Service for an OAuth token which is signed with the RSA private key.
|
||||||
|
The server then responds with an OAuth token that grants permission to access the message queue (HTTP long poll), allowing the runner to acquire the messages it will eventually run.
|
||||||
|
|
||||||
|
## Run a workflow
|
||||||
|
|
||||||
|
When a workflow is run, its labels are evaluated, it is matched to a runner and a message is placed in a queue of messages for that runner.
|
||||||
|
The runner then starts listening for jobs via the message queue HTTP long poll.
|
||||||
|
The message is encrypted with the runner's public key, stored during runner configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A workflow is queued as a result of a triggered [event](https://help.github.com/en/actions/reference/events-that-trigger-workflows). Workflows can be scheduled to [run at specific UTC times](https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule) using POSIX `cron` syntax.
|
||||||
|
An [OAuth token](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) is generated, granting limited access to the host in Actions Service associated with the github.com repository/organization.
|
||||||
|
The lifetime of the OAuth token is the lifetime of the run or at most the [job timeout (default: 6 hours)](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes), plus 10 additional minutes.
|
||||||
|
|
||||||
|
## Accessing GitHub resources
|
||||||
|
|
||||||
|
The job message sent to the runner contains the OAuth token to talk back to the Actions Service.
|
||||||
|
The runner listener parent process will spawn a runner worker process for that job and send it the job message over IPC.
|
||||||
|
The token is never persisted.
|
||||||
|
|
||||||
|
Each action is run as a unique subprocess.
|
||||||
|
The encrypted access token will be provided as an environment variable in each action subprocess.
|
||||||
|
The token is registered with the runner as a secret and scrubbed from the logs as they are written.
|
||||||
|
|
||||||
|
Authentication in a workflow run to github.com can be accomplished by using the [`GITHUB_TOKEN`](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#about-the-github_token-secret)) secret. This token expires after 60 minutes. Please note that this token is different from the OAuth token that the runner uses to talk to the Actions Service.
|
||||||
|
|
||||||
|
## Hosted runner authentication
|
||||||
|
|
||||||
|
Hosted runner authentication differs from self-hosted authentication in that runners do not undergo a registration process, but instead, the hosted runners get the OAuth token directly by reading the `.credentials` file. The scope of this particular token is limited for a given workflow job execution, and the token is revoked as soon as the job is finished.
|
||||||
|
|
||||||
|

|
||||||
BIN
docs/res/hosted-config-start.png
Normal file
BIN
docs/res/hosted-config-start.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
52
docs/res/runner-auth-diags.txt
Normal file
52
docs/res/runner-auth-diags.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Markup used to generate the runner auth diagrams: https://websequencediagrams.com
|
||||||
|
|
||||||
|
title Runner Configuration (self-hosted only)
|
||||||
|
|
||||||
|
note left of Runner: GitHub repo URL as input
|
||||||
|
Runner->github.com: Retrieve Actions Service access using runner registration token
|
||||||
|
github.com->Runner: Access token for Actions Service
|
||||||
|
note left of Runner: Generate RSA key pair
|
||||||
|
note left of Runner: Store encrypted RSA private key on disk
|
||||||
|
Runner->Actions Service: Register runner using Actions Service access token
|
||||||
|
note right of Runner: Runner name, RSA public key sent
|
||||||
|
note right of Actions Service: Public key stored
|
||||||
|
Actions Service->Token Service: Register runner as an app along with the RSA public key
|
||||||
|
note right of Token Service: Public key stored
|
||||||
|
Token Service->Actions Service: Client Id for the runner application
|
||||||
|
Actions Service->Runner: Client Id and Token Endpoint URL
|
||||||
|
note left of Runner: Store runner configuration info into .runner file
|
||||||
|
note left of Runner: Store Token registration info into .credentials file
|
||||||
|
|
||||||
|
title Runner Start and Running (self-hosted only)
|
||||||
|
|
||||||
|
Runner.Listener->Runner.Listener: Start
|
||||||
|
note left of Runner.Listener: Load config info from .runner
|
||||||
|
note left of Runner.Listener: Load token registration from .credentials
|
||||||
|
Runner.Listener->Token Service: Exchange OAuth token (happens every 50 mins)
|
||||||
|
note right of Runner.Listener: Construct JWT token, use Client Id signed by RSA private key
|
||||||
|
note left of Actions Service: Find corresponding RSA public key, use Client Id\nVerify JWT token's signature
|
||||||
|
Token Service->Runner.Listener: OAuth token with limited permission and valid for 50 mins
|
||||||
|
Runner.Listener->Actions Service: Connect to Actions Service with OAuth token
|
||||||
|
Actions Service->Runner.Listener: Workflow job
|
||||||
|
|
||||||
|
title Running workflow
|
||||||
|
|
||||||
|
Runner.Listener->Service (Message Queue): Get message
|
||||||
|
note right of Runner.Listener: Authenticate with exchanged OAuth token
|
||||||
|
Event->Actions Service: Queue workflow
|
||||||
|
Actions Service->Actions Service: Generate OAuth token per job
|
||||||
|
Actions Service->Actions Service: Build job message with the OAuth token
|
||||||
|
Actions Service->Actions Service: Encrypt job message with the target runner's public key
|
||||||
|
Actions Service->Service (Message Queue): Send encrypted job message to runner
|
||||||
|
Service (Message Queue)->Runner.Listener: Send job
|
||||||
|
note right of Runner.Listener: Decrypt message with runner's private key
|
||||||
|
Runner.Listener->Runner.Worker: Create worker process per job and run the job
|
||||||
|
|
||||||
|
title Runner Configuration, Start and Running (hosted only)
|
||||||
|
|
||||||
|
Machine Management Service->Runner.Listener: Construct .runner configuration file, store token in .credentials
|
||||||
|
Runner.Listener->Runner.Listener: Start
|
||||||
|
note left of Runner.Listener: Load config info from .runner
|
||||||
|
note left of Runner.Listener: Load OAuth token from .credentials
|
||||||
|
Runner.Listener->Actions Service: Connect to Actions Service with OAuth token in .credentials
|
||||||
|
Actions Service->Runner.Listener: Workflow job
|
||||||
BIN
docs/res/self-hosted-config.png
Normal file
BIN
docs/res/self-hosted-config.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
docs/res/self-hosted-start.png
Normal file
BIN
docs/res/self-hosted-start.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/res/workflow-run.png
Normal file
BIN
docs/res/workflow-run.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -1,27 +1,24 @@
|
|||||||
## Features
|
## Features
|
||||||
- Expose whether debug is on/off via RUNNER_DEBUG. (#253)
|
- Update Runner Register GitHub API URL to Support Org-level Runner (#339 #345 #352)
|
||||||
- Upload log on runner when worker get killed due to cancellation timeout. (#255)
|
- Preserve workflow file/line/column for better error messages (#356)
|
||||||
- Update config.sh/cmd --help documentation (#282)
|
- Switch to use token service instead of SPS for exchanging oauth token. (#325)
|
||||||
- Set http_proxy and related env vars for job/service containers (#304)
|
- Load and print machine setup info from .setup_info (#364)
|
||||||
- Set both http_proxy and HTTP_PROXY env for runner/worker processes. (#298)
|
- Expose job name as $GITHUB_JOB (#366)
|
||||||
|
- Add support for job outputs. (#365)
|
||||||
|
- Set CI=true when launch process in actions runner. (#374)
|
||||||
|
- Set steps.<id>.outcome and steps.<id>.conclusion. (#372)
|
||||||
|
- Add support for workflow/job defaults. (#369)
|
||||||
|
- Expose GITHUB_REPOSITORY_OWNER and ${{github.repository_owner}}. (#378)
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Verify runner Windows service hash started successfully after configuration (#236)
|
- Use authenticate endpoint for testing runner connection. (#311)
|
||||||
- Detect source file path in L0 without using env. (#257)
|
- Commands translate file path from container action (#331)
|
||||||
- Handle escaped '%' in commands data section (#200)
|
- Change problem matchers output to debug (#363)
|
||||||
- Allow container to be null/empty during matrix expansion (#266)
|
- Switch hashFiles to extension function (#362)
|
||||||
- Translate problem matcher file to host path (#272)
|
- Add expanded volumes strings to container mounts (#384)
|
||||||
- Change hashFiles() expression function to use @actions/glob. (#268)
|
|
||||||
- Default post-job action's condition to always(). (#293)
|
|
||||||
- Support action.yaml file as action's entry file (#288)
|
|
||||||
- Trace javascript action exit code to debug instead of user logs (#290)
|
|
||||||
- Change prompt message when removing a runner to lines up with GitHub.com UI (#303)
|
|
||||||
- Include step.env as part of env context. (#300)
|
|
||||||
- Update Base64 Encoders to deal with suffixes (#284)
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Move .sln file under ./src (#238)
|
- Add runner auth documentation (#357)
|
||||||
- Treat warnings as errors during compile (#249)
|
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows
|
||||||
|
|||||||
@@ -78,8 +78,10 @@ namespace GitHub.Runner.Common
|
|||||||
bool IsServiceConfigured();
|
bool IsServiceConfigured();
|
||||||
bool HasCredentials();
|
bool HasCredentials();
|
||||||
CredentialData GetCredentials();
|
CredentialData GetCredentials();
|
||||||
|
CredentialData GetMigratedCredentials();
|
||||||
RunnerSettings GetSettings();
|
RunnerSettings GetSettings();
|
||||||
void SaveCredential(CredentialData credential);
|
void SaveCredential(CredentialData credential);
|
||||||
|
void SaveMigratedCredential(CredentialData credential);
|
||||||
void SaveSettings(RunnerSettings settings);
|
void SaveSettings(RunnerSettings settings);
|
||||||
void DeleteCredential();
|
void DeleteCredential();
|
||||||
void DeleteSettings();
|
void DeleteSettings();
|
||||||
@@ -90,9 +92,11 @@ namespace GitHub.Runner.Common
|
|||||||
private string _binPath;
|
private string _binPath;
|
||||||
private string _configFilePath;
|
private string _configFilePath;
|
||||||
private string _credFilePath;
|
private string _credFilePath;
|
||||||
|
private string _migratedCredFilePath;
|
||||||
private string _serviceConfigFilePath;
|
private string _serviceConfigFilePath;
|
||||||
|
|
||||||
private CredentialData _creds;
|
private CredentialData _creds;
|
||||||
|
private CredentialData _migratedCreds;
|
||||||
private RunnerSettings _settings;
|
private RunnerSettings _settings;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
@@ -114,6 +118,9 @@ namespace GitHub.Runner.Common
|
|||||||
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||||
Trace.Info("CredFilePath: {0}", _credFilePath);
|
Trace.Info("CredFilePath: {0}", _credFilePath);
|
||||||
|
|
||||||
|
_migratedCredFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedCredentials);
|
||||||
|
Trace.Info("MigratedCredFilePath: {0}", _migratedCredFilePath);
|
||||||
|
|
||||||
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
|
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
|
||||||
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
|
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
|
||||||
}
|
}
|
||||||
@@ -123,7 +130,7 @@ namespace GitHub.Runner.Common
|
|||||||
public bool HasCredentials()
|
public bool HasCredentials()
|
||||||
{
|
{
|
||||||
Trace.Info("HasCredentials()");
|
Trace.Info("HasCredentials()");
|
||||||
bool credsStored = (new FileInfo(_credFilePath)).Exists;
|
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists;
|
||||||
Trace.Info("stored {0}", credsStored);
|
Trace.Info("stored {0}", credsStored);
|
||||||
return credsStored;
|
return credsStored;
|
||||||
}
|
}
|
||||||
@@ -154,6 +161,16 @@ namespace GitHub.Runner.Common
|
|||||||
return _creds;
|
return _creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CredentialData GetMigratedCredentials()
|
||||||
|
{
|
||||||
|
if (_migratedCreds == null && File.Exists(_migratedCredFilePath))
|
||||||
|
{
|
||||||
|
_migratedCreds = IOUtil.LoadObject<CredentialData>(_migratedCredFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _migratedCreds;
|
||||||
|
}
|
||||||
|
|
||||||
public RunnerSettings GetSettings()
|
public RunnerSettings GetSettings()
|
||||||
{
|
{
|
||||||
if (_settings == null)
|
if (_settings == null)
|
||||||
@@ -188,6 +205,21 @@ namespace GitHub.Runner.Common
|
|||||||
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
|
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SaveMigratedCredential(CredentialData credential)
|
||||||
|
{
|
||||||
|
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
|
||||||
|
if (File.Exists(_migratedCredFilePath))
|
||||||
|
{
|
||||||
|
// Delete existing credential file first, since the file is hidden and not able to overwrite.
|
||||||
|
Trace.Info("Delete exist runner migrated credential file.");
|
||||||
|
IOUtil.DeleteFile(_migratedCredFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOUtil.SaveObject(credential, _migratedCredFilePath);
|
||||||
|
Trace.Info("Migrated Credentials Saved.");
|
||||||
|
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
public void SaveSettings(RunnerSettings settings)
|
public void SaveSettings(RunnerSettings settings)
|
||||||
{
|
{
|
||||||
Trace.Info("Saving runner settings.");
|
Trace.Info("Saving runner settings.");
|
||||||
@@ -206,6 +238,7 @@ namespace GitHub.Runner.Common
|
|||||||
public void DeleteCredential()
|
public void DeleteCredential()
|
||||||
{
|
{
|
||||||
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
||||||
|
IOUtil.Delete(_migratedCredFilePath, default(CancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteSettings()
|
public void DeleteSettings()
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Runner,
|
Runner,
|
||||||
Credentials,
|
Credentials,
|
||||||
|
MigratedCredentials,
|
||||||
RSACredentials,
|
RSACredentials,
|
||||||
Service,
|
Service,
|
||||||
CredentialStore,
|
CredentialStore,
|
||||||
Certificates,
|
Certificates,
|
||||||
Options,
|
Options,
|
||||||
|
SetupInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ namespace GitHub.Runner.Common
|
|||||||
Add<T>(extensions, "GitHub.Runner.Worker.SetOutputCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.SetOutputCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.SaveStateCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.SaveStateCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.AddPathCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.AddPathCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.RefreshTokenCommandExtension, Runner.Worker");
|
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.AddMaskCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.AddMaskCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.AddMatcherCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.AddMatcherCommandExtension, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.RemoveMatcherCommandExtension, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.RemoveMatcherCommandExtension, Runner.Worker");
|
||||||
|
|||||||
@@ -281,6 +281,12 @@ namespace GitHub.Runner.Common
|
|||||||
".credentials");
|
".credentials");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.MigratedCredentials:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".credentials_migrated");
|
||||||
|
break;
|
||||||
|
|
||||||
case WellKnownConfigFile.RSACredentials:
|
case WellKnownConfigFile.RSACredentials:
|
||||||
path = Path.Combine(
|
path = Path.Combine(
|
||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
@@ -316,6 +322,13 @@ namespace GitHub.Runner.Common
|
|||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
".options");
|
".options");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.SetupInfo:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".setup_info");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ namespace GitHub.Runner.Common
|
|||||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||||
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
||||||
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||||
Task<GitHubToken> RefreshGitHubTokenAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid jobId, CancellationToken cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class JobServer : RunnerService, IJobServer
|
public sealed class JobServer : RunnerService, IJobServer
|
||||||
@@ -114,11 +113,5 @@ namespace GitHub.Runner.Common
|
|||||||
CheckConnection();
|
CheckConnection();
|
||||||
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<GitHubToken> RefreshGitHubTokenAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid jobId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
return _taskClient.RefreshTokenAsync(scopeIdentifier, hubName, planId, jobId, cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
// agent update
|
// agent update
|
||||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState);
|
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState);
|
||||||
|
|
||||||
|
// runner authorization url
|
||||||
|
Task<string> GetRunnerAuthUrlAsync(int runnerPoolId, int runnerId);
|
||||||
|
Task ReportRunnerAuthUrlErrorAsync(int runnerPoolId, int runnerId, string error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RunnerServer : RunnerService, IRunnerServer
|
public sealed class RunnerServer : RunnerService, IRunnerServer
|
||||||
@@ -334,5 +338,20 @@ namespace GitHub.Runner.Common
|
|||||||
CheckConnection(RunnerConnectionType.Generic);
|
CheckConnection(RunnerConnectionType.Generic);
|
||||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState);
|
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// Runner Auth Url
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
public Task<string> GetRunnerAuthUrlAsync(int runnerPoolId, int runnerId)
|
||||||
|
{
|
||||||
|
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||||
|
return _messageTaskAgentClient.GetAgentAuthUrlAsync(runnerPoolId, runnerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ReportRunnerAuthUrlErrorAsync(int runnerPoolId, int runnerId, string error)
|
||||||
|
{
|
||||||
|
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||||
|
return _messageTaskAgentClient.ReportAgentAuthUrlMigrationErrorAsync(runnerPoolId, runnerId, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public interface ICredentialManager : IRunnerService
|
public interface ICredentialManager : IRunnerService
|
||||||
{
|
{
|
||||||
ICredentialProvider GetCredentialProvider(string credType);
|
ICredentialProvider GetCredentialProvider(string credType);
|
||||||
VssCredentials LoadCredentials();
|
VssCredentials LoadCredentials(bool preferMigrated = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CredentialManager : RunnerService, ICredentialManager
|
public class CredentialManager : RunnerService, ICredentialManager
|
||||||
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VssCredentials LoadCredentials()
|
public VssCredentials LoadCredentials(bool preferMigrated = true)
|
||||||
{
|
{
|
||||||
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
||||||
|
|
||||||
@@ -50,6 +50,16 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
CredentialData credData = store.GetCredentials();
|
CredentialData credData = store.GetCredentials();
|
||||||
|
|
||||||
|
if (preferMigrated)
|
||||||
|
{
|
||||||
|
var migratedCred = store.GetMigratedCredentials();
|
||||||
|
if (migratedCred != null)
|
||||||
|
{
|
||||||
|
credData = migratedCred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
||||||
credProv.CredentialData = credData;
|
credProv.CredentialData = credData;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
@@ -29,7 +28,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||||
|
|
||||||
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
||||||
var oathEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(clientId, nameof(clientId));
|
ArgUtil.NotNullOrEmpty(clientId, nameof(clientId));
|
||||||
ArgUtil.NotNullOrEmpty(authorizationUrl, nameof(authorizationUrl));
|
ArgUtil.NotNullOrEmpty(authorizationUrl, nameof(authorizationUrl));
|
||||||
@@ -39,7 +38,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
var keyManager = context.GetService<IRSAKeyManager>();
|
var keyManager = context.GetService<IRSAKeyManager>();
|
||||||
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
||||||
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
||||||
var agentCredential = new VssOAuthCredential(new Uri(oathEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
||||||
|
|
||||||
// Construct a credentials cache with a single OAuth credential for communication. The windows credential
|
// Construct a credentials cache with a single OAuth credential for communication. The windows credential
|
||||||
// is explicitly set to null to ensure we never do that negotiation.
|
// is explicitly set to null to ensure we never do that negotiation.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace GitHub.Runner.Listener
|
|||||||
[ServiceLocator(Default = typeof(JobDispatcher))]
|
[ServiceLocator(Default = typeof(JobDispatcher))]
|
||||||
public interface IJobDispatcher : IRunnerService
|
public interface IJobDispatcher : IRunnerService
|
||||||
{
|
{
|
||||||
|
bool Busy { get; }
|
||||||
TaskCompletionSource<bool> RunOnceJobCompleted { get; }
|
TaskCompletionSource<bool> RunOnceJobCompleted { get; }
|
||||||
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
||||||
bool Cancel(JobCancelMessage message);
|
bool Cancel(JobCancelMessage message);
|
||||||
@@ -69,6 +70,8 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted;
|
public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted;
|
||||||
|
|
||||||
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false)
|
public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false)
|
||||||
{
|
{
|
||||||
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
||||||
@@ -247,7 +250,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task completedTask = await Task.WhenAny(jobDispatch.WorkerDispatch, Task.Delay(TimeSpan.FromSeconds(45)));
|
Task completedTask = await Task.WhenAny(jobDispatch.WorkerDispatch, Task.Delay(TimeSpan.FromSeconds(45)));
|
||||||
if (completedTask != jobDispatch.WorkerDispatch)
|
if (completedTask != jobDispatch.WorkerDispatch)
|
||||||
{
|
{
|
||||||
// at this point, the job exectuion might encounter some dead lock and even not able to be canclled.
|
// at this point, the job execution might encounter some dead lock and even not able to be cancelled.
|
||||||
// no need to localize the exception string should never happen.
|
// no need to localize the exception string should never happen.
|
||||||
throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds.");
|
throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds.");
|
||||||
}
|
}
|
||||||
@@ -295,6 +298,9 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
||||||
|
{
|
||||||
|
Busy = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (previousJobDispatch != null)
|
if (previousJobDispatch != null)
|
||||||
{
|
{
|
||||||
@@ -595,6 +601,11 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ using System.Diagnostics;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Test")]
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
[ServiceLocator(Default = typeof(MessageListener))]
|
[ServiceLocator(Default = typeof(MessageListener))]
|
||||||
@@ -32,18 +35,30 @@ namespace GitHub.Runner.Listener
|
|||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private TaskAgentSession _session;
|
private TaskAgentSession _session;
|
||||||
|
private ICredentialManager _credMgr;
|
||||||
|
private IConfigurationStore _configStore;
|
||||||
private TimeSpan _getNextMessageRetryInterval;
|
private TimeSpan _getNextMessageRetryInterval;
|
||||||
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
||||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
|
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
// Whether load credentials from .credentials_migrated file
|
||||||
|
internal bool _useMigratedCredentials;
|
||||||
|
|
||||||
|
// need to check auth url if there is only .credentials and auth schema is OAuth
|
||||||
|
internal bool _needToCheckAuthorizationUrlUpdate;
|
||||||
|
internal Task<VssCredentials> _authorizationUrlMigrationBackgroundTask;
|
||||||
|
internal Task _authorizationUrlRollbackReattemptDelayBackgroundTask;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
|
|
||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
|
_credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
|
_configStore = HostContext.GetService<IConfigurationStore>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
||||||
@@ -58,8 +73,8 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection.
|
// Create connection.
|
||||||
Trace.Info("Loading Credentials");
|
Trace.Info("Loading Credentials");
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
_useMigratedCredentials = !StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_SPSAUTHURL"));
|
||||||
VssCredentials creds = credMgr.LoadCredentials();
|
VssCredentials creds = _credMgr.LoadCredentials(_useMigratedCredentials);
|
||||||
|
|
||||||
var agent = new TaskAgentReference
|
var agent = new TaskAgentReference
|
||||||
{
|
{
|
||||||
@@ -74,6 +89,17 @@ namespace GitHub.Runner.Listener
|
|||||||
string errorMessage = string.Empty;
|
string errorMessage = string.Empty;
|
||||||
bool encounteringError = false;
|
bool encounteringError = false;
|
||||||
|
|
||||||
|
var originalCreds = _configStore.GetCredentials();
|
||||||
|
var migratedCreds = _configStore.GetMigratedCredentials();
|
||||||
|
if (migratedCreds == null)
|
||||||
|
{
|
||||||
|
_useMigratedCredentials = false;
|
||||||
|
if (originalCreds.Scheme == Constants.Configuration.OAuth)
|
||||||
|
{
|
||||||
|
_needToCheckAuthorizationUrlUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
@@ -101,6 +127,12 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = false;
|
encounteringError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_needToCheckAuthorizationUrlUpdate)
|
||||||
|
{
|
||||||
|
// start background task try to get new authorization url
|
||||||
|
_authorizationUrlMigrationBackgroundTask = GetNewOAuthAuthorizationSetting(token);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
@@ -119,10 +151,23 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
if (!IsSessionCreationExceptionRetriable(ex))
|
if (!IsSessionCreationExceptionRetriable(ex))
|
||||||
|
{
|
||||||
|
if (_useMigratedCredentials)
|
||||||
|
{
|
||||||
|
// migrated credentials might cause lose permission during permission check,
|
||||||
|
// we will force to use original credential and try again
|
||||||
|
_useMigratedCredentials = false;
|
||||||
|
var reattemptBackoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromHours(24), TimeSpan.FromHours(36));
|
||||||
|
_authorizationUrlRollbackReattemptDelayBackgroundTask = HostContext.Delay(reattemptBackoff, token); // retry migrated creds in 24-36 hours.
|
||||||
|
creds = _credMgr.LoadCredentials(false);
|
||||||
|
Trace.Error("Fallback to original credentials and try again.");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!encounteringError) //print the message only on the first error
|
if (!encounteringError) //print the message only on the first error
|
||||||
{
|
{
|
||||||
@@ -182,6 +227,51 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = false;
|
encounteringError = false;
|
||||||
continuousError = 0;
|
continuousError = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_needToCheckAuthorizationUrlUpdate &&
|
||||||
|
_authorizationUrlMigrationBackgroundTask?.IsCompleted == true)
|
||||||
|
{
|
||||||
|
if (HostContext.GetService<IJobDispatcher>().Busy ||
|
||||||
|
HostContext.GetService<ISelfUpdater>().Busy)
|
||||||
|
{
|
||||||
|
Trace.Info("Job or runner updates in progress, update credentials next time.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newCred = await _authorizationUrlMigrationBackgroundTask;
|
||||||
|
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), newCred);
|
||||||
|
Trace.Info("Updated connection to use migrated credential for next GetMessage call.");
|
||||||
|
_useMigratedCredentials = true;
|
||||||
|
_authorizationUrlMigrationBackgroundTask = null;
|
||||||
|
_needToCheckAuthorizationUrlUpdate = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Fail to refresh connection with new authorization url.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_authorizationUrlRollbackReattemptDelayBackgroundTask?.IsCompleted == true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// we rolled back to use original creds about 2 days before, now it's a good time to try migrated creds again.
|
||||||
|
Trace.Info("Re-attempt to use migrated credential");
|
||||||
|
var migratedCreds = _credMgr.LoadCredentials();
|
||||||
|
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), migratedCreds);
|
||||||
|
_useMigratedCredentials = true;
|
||||||
|
_authorizationUrlRollbackReattemptDelayBackgroundTask = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Fail to refresh connection with new authorization url on rollback reattempt.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -204,9 +294,23 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
|
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
|
||||||
}
|
}
|
||||||
else if (!IsGetNextMessageExceptionRetriable(ex))
|
else if (!IsGetNextMessageExceptionRetriable(ex))
|
||||||
|
{
|
||||||
|
if (_useMigratedCredentials)
|
||||||
|
{
|
||||||
|
// migrated credentials might cause lose permission during permission check,
|
||||||
|
// we will force to use original credential and try again
|
||||||
|
_useMigratedCredentials = false;
|
||||||
|
var reattemptBackoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromHours(24), TimeSpan.FromHours(36));
|
||||||
|
_authorizationUrlRollbackReattemptDelayBackgroundTask = HostContext.Delay(reattemptBackoff, token); // retry migrated creds in 24-36 hours.
|
||||||
|
var originalCreds = _credMgr.LoadCredentials(false);
|
||||||
|
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), originalCreds);
|
||||||
|
Trace.Error("Fallback to original credentials and try again.");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
continuousError++;
|
continuousError++;
|
||||||
@@ -397,5 +501,80 @@ namespace GitHub.Runner.Listener
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<VssCredentials> GetNewOAuthAuthorizationSetting(CancellationToken token)
|
||||||
|
{
|
||||||
|
Trace.Info("Start checking oauth authorization url update.");
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(45));
|
||||||
|
await HostContext.Delay(backoff, token);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var migratedAuthorizationUrl = await _runnerServer.GetRunnerAuthUrlAsync(_settings.PoolId, _settings.AgentId);
|
||||||
|
if (!string.IsNullOrEmpty(migratedAuthorizationUrl))
|
||||||
|
{
|
||||||
|
var credData = _configStore.GetCredentials();
|
||||||
|
var clientId = credData.Data.GetValueOrDefault("clientId", null);
|
||||||
|
var currentAuthorizationUrl = credData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||||
|
Trace.Info($"Current authorization url: {currentAuthorizationUrl}, new authorization url: {migratedAuthorizationUrl}");
|
||||||
|
|
||||||
|
if (string.Equals(currentAuthorizationUrl, migratedAuthorizationUrl, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// We don't need to update credentials.
|
||||||
|
Trace.Info("No needs to update authorization url");
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(-1), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
|
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
||||||
|
|
||||||
|
var migratedClientCredential = new VssOAuthJwtBearerClientCredential(clientId, migratedAuthorizationUrl, signingCredentials);
|
||||||
|
var migratedRunnerCredential = new VssOAuthCredential(new Uri(migratedAuthorizationUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, migratedClientCredential);
|
||||||
|
|
||||||
|
Trace.Info("Try connect service with Token Service OAuth endpoint.");
|
||||||
|
var runnerServer = HostContext.CreateService<IRunnerServer>();
|
||||||
|
await runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), migratedRunnerCredential);
|
||||||
|
await runnerServer.GetAgentPoolsAsync();
|
||||||
|
Trace.Info($"Successfully connected service with new authorization url.");
|
||||||
|
|
||||||
|
var migratedCredData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth,
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
{ "clientId", clientId },
|
||||||
|
{ "authorizationUrl", migratedAuthorizationUrl },
|
||||||
|
{ "oauthEndpointUrl", migratedAuthorizationUrl },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
_configStore.SaveMigratedCredential(migratedCredData);
|
||||||
|
return migratedRunnerCredential;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.Verbose("No authorization url updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Fail to get/test new authorization url.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _runnerServer.ReportRunnerAuthUrlErrorAsync(_settings.PoolId, _settings.AgentId, ex.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// best effort
|
||||||
|
Trace.Error("Fail to report the migration error");
|
||||||
|
Trace.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace GitHub.Runner.Listener
|
|||||||
[ServiceLocator(Default = typeof(SelfUpdater))]
|
[ServiceLocator(Default = typeof(SelfUpdater))]
|
||||||
public interface ISelfUpdater : IRunnerService
|
public interface ISelfUpdater : IRunnerService
|
||||||
{
|
{
|
||||||
|
bool Busy { get; }
|
||||||
Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
|
Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +32,8 @@ namespace GitHub.Runner.Listener
|
|||||||
private int _poolId;
|
private int _poolId;
|
||||||
private int _agentId;
|
private int _agentId;
|
||||||
|
|
||||||
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
@@ -44,6 +47,9 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
|
public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
|
||||||
|
{
|
||||||
|
Busy = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
|
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
|
||||||
{
|
{
|
||||||
@@ -92,6 +98,11 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token)
|
private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -271,6 +271,14 @@ namespace GitHub.Runner.Sdk
|
|||||||
// Indicate GitHub Actions process.
|
// Indicate GitHub Actions process.
|
||||||
_proc.StartInfo.Environment["GITHUB_ACTIONS"] = "true";
|
_proc.StartInfo.Environment["GITHUB_ACTIONS"] = "true";
|
||||||
|
|
||||||
|
// Set CI=true when no one else already set it.
|
||||||
|
// CI=true is common set in most CI provider in GitHub
|
||||||
|
if (!_proc.StartInfo.Environment.ContainsKey("CI") &&
|
||||||
|
Environment.GetEnvironmentVariable("CI") == null)
|
||||||
|
{
|
||||||
|
_proc.StartInfo.Environment["CI"] = "true";
|
||||||
|
}
|
||||||
|
|
||||||
// Hook up the events.
|
// Hook up the events.
|
||||||
_proc.EnableRaisingEvents = true;
|
_proc.EnableRaisingEvents = true;
|
||||||
_proc.Exited += ProcessExitedHandler;
|
_proc.Exited += ProcessExitedHandler;
|
||||||
|
|||||||
@@ -288,30 +288,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RefreshTokenCommandExtension : RunnerService, IActionCommandExtension
|
|
||||||
{
|
|
||||||
public string Command => "refresh-token";
|
|
||||||
public bool OmitEcho => false;
|
|
||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNullOrEmpty(command.Data, "token file");
|
|
||||||
var runnerTemp = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
|
||||||
var tempFile = Path.Combine(runnerTemp, command.Data);
|
|
||||||
if (!tempFile.StartsWith(runnerTemp + Path.DirectorySeparatorChar))
|
|
||||||
{
|
|
||||||
throw new Exception($"'{command.Data}' has to be under runner temp directory '{runnerTemp}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info("Here");
|
|
||||||
var githubToken = context.GetGitHubToken().GetAwaiter().GetResult();
|
|
||||||
File.WriteAllText(tempFile, githubToken);
|
|
||||||
Trace.Info("HereAgain");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AddMatcherCommandExtension : RunnerService, IActionCommandExtension
|
public sealed class AddMatcherCommandExtension : RunnerService, IActionCommandExtension
|
||||||
{
|
{
|
||||||
public string Command => "add-matcher";
|
public string Command => "add-matcher";
|
||||||
|
|||||||
@@ -21,24 +21,11 @@ using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplat
|
|||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
public class PrepareResult
|
|
||||||
{
|
|
||||||
public PrepareResult(List<JobExtensionRunner> containerSetupSteps, Dictionary<Guid, IActionRunner> preStepTracker)
|
|
||||||
{
|
|
||||||
this.ContainerSetupSteps = containerSetupSteps;
|
|
||||||
this.PreStepTracker = preStepTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<JobExtensionRunner> ContainerSetupSteps { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<Guid, IActionRunner> PreStepTracker { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ActionManager))]
|
[ServiceLocator(Default = typeof(ActionManager))]
|
||||||
public interface IActionManager : IRunnerService
|
public interface IActionManager : IRunnerService
|
||||||
{
|
{
|
||||||
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
||||||
Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
||||||
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +39,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
||||||
|
|
||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
public async Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
ArgUtil.NotNull(steps, nameof(steps));
|
ArgUtil.NotNull(steps, nameof(steps));
|
||||||
@@ -62,7 +49,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
||||||
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
|
||||||
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
|
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
|
||||||
Dictionary<Guid, IActionRunner> preStepTracker = new Dictionary<Guid, IActionRunner>();
|
|
||||||
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
||||||
|
|
||||||
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
|
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
|
||||||
@@ -125,21 +111,6 @@ namespace GitHub.Runner.Worker
|
|||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
|
||||||
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
|
||||||
{
|
|
||||||
var definition = LoadAction(executionContext, action);
|
|
||||||
if (definition.Data.Execution.HasInit)
|
|
||||||
{
|
|
||||||
var actionRunner = HostContext.CreateService<IActionRunner>();
|
|
||||||
actionRunner.Action = action;
|
|
||||||
actionRunner.Stage = ActionRunStage.Pre;
|
|
||||||
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
|
||||||
|
|
||||||
preStepTracker[action.Id] = actionRunner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +147,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return new PrepareResult(containerSetupSteps, preStepTracker);
|
return containerSetupSteps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
||||||
@@ -191,7 +162,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Data = new ActionDefinitionData()
|
Data = new ActionDefinitionData()
|
||||||
};
|
};
|
||||||
|
|
||||||
// bool localAction = false;
|
|
||||||
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
||||||
{
|
{
|
||||||
Trace.Info("Load action that reference container from registry.");
|
Trace.Info("Load action that reference container from registry.");
|
||||||
@@ -210,7 +180,6 @@ namespace GitHub.Runner.Worker
|
|||||||
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// localAction = true;
|
|
||||||
actionDirectory = executionContext.GetGitHubContext("workspace");
|
actionDirectory = executionContext.GetGitHubContext("workspace");
|
||||||
if (!string.IsNullOrEmpty(repoAction.Path))
|
if (!string.IsNullOrEmpty(repoAction.Path))
|
||||||
{
|
{
|
||||||
@@ -270,11 +239,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action container env: {StringUtil.ConvertToJson(containerAction.Environment)}.");
|
Trace.Info($"Action container env: {StringUtil.ConvertToJson(containerAction.Environment)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(containerAction.Init))
|
|
||||||
{
|
|
||||||
Trace.Info($"Action container init entrypoint: {containerAction.Init}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(containerAction.EntryPoint))
|
if (!string.IsNullOrEmpty(containerAction.EntryPoint))
|
||||||
{
|
{
|
||||||
Trace.Info($"Action container entrypoint: {containerAction.EntryPoint}.");
|
Trace.Info($"Action container entrypoint: {containerAction.EntryPoint}.");
|
||||||
@@ -294,7 +258,6 @@ namespace GitHub.Runner.Worker
|
|||||||
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.NodeJS)
|
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.NodeJS)
|
||||||
{
|
{
|
||||||
var nodeAction = definition.Data.Execution as NodeJSActionExecutionData;
|
var nodeAction = definition.Data.Execution as NodeJSActionExecutionData;
|
||||||
Trace.Info($"Action init node.js file: {nodeAction.Init ?? "N/A"}.");
|
|
||||||
Trace.Info($"Action node.js file: {nodeAction.Script}.");
|
Trace.Info($"Action node.js file: {nodeAction.Script}.");
|
||||||
Trace.Info($"Action cleanup node.js file: {nodeAction.Cleanup ?? "N/A"}.");
|
Trace.Info($"Action cleanup node.js file: {nodeAction.Cleanup ?? "N/A"}.");
|
||||||
}
|
}
|
||||||
@@ -809,8 +772,6 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Container;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Container;
|
||||||
|
|
||||||
public override bool HasInit => !string.IsNullOrEmpty(Init);
|
|
||||||
public override bool HasMain => !string.IsNullOrEmpty(EntryPoint);
|
|
||||||
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
||||||
|
|
||||||
public string Image { get; set; }
|
public string Image { get; set; }
|
||||||
@@ -821,8 +782,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public MappingToken Environment { get; set; }
|
public MappingToken Environment { get; set; }
|
||||||
|
|
||||||
public string Init { get; set; }
|
|
||||||
|
|
||||||
public string Cleanup { get; set; }
|
public string Cleanup { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,14 +789,10 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.NodeJS;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.NodeJS;
|
||||||
|
|
||||||
public override bool HasInit => !string.IsNullOrEmpty(Init);
|
|
||||||
public override bool HasMain => !string.IsNullOrEmpty(Script);
|
|
||||||
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
||||||
|
|
||||||
public string Script { get; set; }
|
public string Script { get; set; }
|
||||||
|
|
||||||
public string Init { get; set; }
|
|
||||||
|
|
||||||
public string Cleanup { get; set; }
|
public string Cleanup { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,9 +800,6 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin;
|
||||||
|
|
||||||
public override bool HasInit => false;
|
|
||||||
public override bool HasMain => true;
|
|
||||||
|
|
||||||
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
||||||
|
|
||||||
public string Plugin { get; set; }
|
public string Plugin { get; set; }
|
||||||
@@ -858,20 +810,16 @@ namespace GitHub.Runner.Worker
|
|||||||
public sealed class ScriptActionExecutionData : ActionExecutionData
|
public sealed class ScriptActionExecutionData : ActionExecutionData
|
||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Script;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Script;
|
||||||
public override bool HasInit => false;
|
|
||||||
public override bool HasMain => true;
|
|
||||||
public override bool HasCleanup => false;
|
public override bool HasCleanup => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class ActionExecutionData
|
public abstract class ActionExecutionData
|
||||||
{
|
{
|
||||||
private string _initCondition = $"{Constants.Expressions.Always}()";
|
|
||||||
private string _cleanupCondition = $"{Constants.Expressions.Always}()";
|
private string _cleanupCondition = $"{Constants.Expressions.Always}()";
|
||||||
|
|
||||||
public abstract ActionExecutionType ExecutionType { get; }
|
public abstract ActionExecutionType ExecutionType { get; }
|
||||||
|
|
||||||
public abstract bool HasInit { get; }
|
|
||||||
public abstract bool HasMain { get; }
|
|
||||||
public abstract bool HasCleanup { get; }
|
public abstract bool HasCleanup { get; }
|
||||||
|
|
||||||
public string CleanupCondition
|
public string CleanupCondition
|
||||||
@@ -879,12 +827,6 @@ namespace GitHub.Runner.Worker
|
|||||||
get { return _cleanupCondition; }
|
get { return _cleanupCondition; }
|
||||||
set { _cleanupCondition = value; }
|
set { _cleanupCondition = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InitCondition
|
|
||||||
{
|
|
||||||
get { return _initCondition; }
|
|
||||||
set { _initCondition = value; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ContainerSetupInfo
|
public class ContainerSetupInfo
|
||||||
|
|||||||
@@ -22,16 +22,17 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
|
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
|
||||||
|
|
||||||
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> contextData);
|
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> contextData);
|
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token, IDictionary<string, PipelineContextData> contextData);
|
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
|
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
|
||||||
{
|
{
|
||||||
private TemplateSchema _actionManifestSchema;
|
private TemplateSchema _actionManifestSchema;
|
||||||
|
private IReadOnlyList<String> _fileTable;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -53,7 +54,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, null);
|
var context = CreateContext(executionContext);
|
||||||
ActionDefinitionData actionDefinition = new ActionDefinitionData();
|
ActionDefinitionData actionDefinition = new ActionDefinitionData();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -61,6 +62,9 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Get the file ID
|
// Get the file ID
|
||||||
var fileId = context.GetFileId(manifestFile);
|
var fileId = context.GetFileId(manifestFile);
|
||||||
|
_fileTable = context.GetFileTable();
|
||||||
|
|
||||||
|
// Read the file
|
||||||
var fileContent = File.ReadAllText(manifestFile);
|
var fileContent = File.ReadAllText(manifestFile);
|
||||||
using (var stringReader = new StringReader(fileContent))
|
using (var stringReader = new StringReader(fileContent))
|
||||||
{
|
{
|
||||||
@@ -129,13 +133,13 @@ namespace GitHub.Runner.Worker
|
|||||||
public List<string> EvaluateContainerArguments(
|
public List<string> EvaluateContainerArguments(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
SequenceToken token,
|
SequenceToken token,
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
|
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, contextData);
|
var context = CreateContext(executionContext, extraExpressionValues);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true);
|
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true);
|
||||||
@@ -168,13 +172,13 @@ namespace GitHub.Runner.Worker
|
|||||||
public Dictionary<string, string> EvaluateContainerEnvironment(
|
public Dictionary<string, string> EvaluateContainerEnvironment(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
MappingToken token,
|
MappingToken token,
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, contextData);
|
var context = CreateContext(executionContext, extraExpressionValues);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true);
|
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true);
|
||||||
@@ -212,13 +216,12 @@ namespace GitHub.Runner.Worker
|
|||||||
public string EvaluateDefaultInput(
|
public string EvaluateDefaultInput(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
string inputName,
|
string inputName,
|
||||||
TemplateToken token,
|
TemplateToken token)
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
|
||||||
{
|
{
|
||||||
string result = "";
|
string result = "";
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, contextData);
|
var context = CreateContext(executionContext);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evaluateResult = TemplateEvaluator.Evaluate(context, "input-default-context", token, 0, null, omitHeader: true);
|
var evaluateResult = TemplateEvaluator.Evaluate(context, "input-default-context", token, 0, null, omitHeader: true);
|
||||||
@@ -243,7 +246,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private TemplateContext CreateContext(
|
private TemplateContext CreateContext(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
IDictionary<string, PipelineContextData> extraExpressionValues = null)
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -257,12 +260,34 @@ namespace GitHub.Runner.Worker
|
|||||||
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (contextData?.Count > 0)
|
// Expression values from execution context
|
||||||
{
|
foreach (var pair in executionContext.ExpressionValues)
|
||||||
foreach (var pair in contextData)
|
|
||||||
{
|
{
|
||||||
result.ExpressionValues[pair.Key] = pair.Value;
|
result.ExpressionValues[pair.Key] = pair.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extra expression values
|
||||||
|
if (extraExpressionValues?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var pair in extraExpressionValues)
|
||||||
|
{
|
||||||
|
result.ExpressionValues[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expression functions from execution context
|
||||||
|
foreach (var item in executionContext.ExpressionFunctions)
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the file table
|
||||||
|
if (_fileTable?.Count > 0)
|
||||||
|
{
|
||||||
|
for (var i = 0 ; i < _fileTable.Count ; i++)
|
||||||
|
{
|
||||||
|
result.GetFileId(_fileTable[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -280,9 +305,6 @@ namespace GitHub.Runner.Worker
|
|||||||
var envToken = default(MappingToken);
|
var envToken = default(MappingToken);
|
||||||
var mainToken = default(StringToken);
|
var mainToken = default(StringToken);
|
||||||
var pluginToken = default(StringToken);
|
var pluginToken = default(StringToken);
|
||||||
var preToken = default(StringToken);
|
|
||||||
var preEntrypointToken = default(StringToken);
|
|
||||||
var preIfToken = default(StringToken);
|
|
||||||
var postToken = default(StringToken);
|
var postToken = default(StringToken);
|
||||||
var postEntrypointToken = default(StringToken);
|
var postEntrypointToken = default(StringToken);
|
||||||
var postIfToken = default(StringToken);
|
var postIfToken = default(StringToken);
|
||||||
@@ -321,15 +343,6 @@ namespace GitHub.Runner.Worker
|
|||||||
case "post-if":
|
case "post-if":
|
||||||
postIfToken = run.Value.AssertString("post-if");
|
postIfToken = run.Value.AssertString("post-if");
|
||||||
break;
|
break;
|
||||||
case "pre":
|
|
||||||
preToken = run.Value.AssertString("pre");
|
|
||||||
break;
|
|
||||||
case "pre-entrypoint":
|
|
||||||
preEntrypointToken = run.Value.AssertString("pre-entrypoint");
|
|
||||||
break;
|
|
||||||
case "pre-if":
|
|
||||||
preIfToken = run.Value.AssertString("pre-if");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore run property {runsKey}.");
|
Trace.Info($"Ignore run property {runsKey}.");
|
||||||
break;
|
break;
|
||||||
@@ -352,8 +365,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Arguments = argsToken,
|
Arguments = argsToken,
|
||||||
EntryPoint = entrypointToken?.Value,
|
EntryPoint = entrypointToken?.Value,
|
||||||
Environment = envToken,
|
Environment = envToken,
|
||||||
Init = preEntrypointToken?.Value,
|
|
||||||
InitCondition = preIfToken?.Value ?? "always()",
|
|
||||||
Cleanup = postEntrypointToken?.Value,
|
Cleanup = postEntrypointToken?.Value,
|
||||||
CleanupCondition = postIfToken?.Value ?? "always()"
|
CleanupCondition = postIfToken?.Value ?? "always()"
|
||||||
};
|
};
|
||||||
@@ -370,8 +381,6 @@ namespace GitHub.Runner.Worker
|
|||||||
return new NodeJSActionExecutionData()
|
return new NodeJSActionExecutionData()
|
||||||
{
|
{
|
||||||
Script = mainToken.Value,
|
Script = mainToken.Value,
|
||||||
Init = preToken?.Value,
|
|
||||||
InitCondition = preIfToken?.Value ?? "always()",
|
|
||||||
Cleanup = postToken?.Value,
|
Cleanup = postToken?.Value,
|
||||||
CleanupCondition = postIfToken?.Value ?? "always()"
|
CleanupCondition = postIfToken?.Value ?? "always()"
|
||||||
};
|
};
|
||||||
@@ -431,566 +440,5 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a YAML file into a TemplateToken
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class YamlObjectReader : IObjectReader
|
|
||||||
{
|
|
||||||
internal YamlObjectReader(
|
|
||||||
Int32? fileId,
|
|
||||||
TextReader input)
|
|
||||||
{
|
|
||||||
m_fileId = fileId;
|
|
||||||
m_parser = new Parser(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowLiteral(out LiteralToken value)
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is Scalar scalar)
|
|
||||||
{
|
|
||||||
// Tag specified
|
|
||||||
if (!string.IsNullOrEmpty(scalar.Tag))
|
|
||||||
{
|
|
||||||
// String tag
|
|
||||||
if (string.Equals(scalar.Tag, c_stringTag, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not plain style
|
|
||||||
if (scalar.Style != ScalarStyle.Plain)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"The scalar style '{scalar.Style}' on line {scalar.Start.Line} and column {scalar.Start.Column} is not valid with the tag '{scalar.Tag}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean, Float, Integer, or Null
|
|
||||||
switch (scalar.Tag)
|
|
||||||
{
|
|
||||||
case c_booleanTag:
|
|
||||||
value = ParseBoolean(scalar);
|
|
||||||
break;
|
|
||||||
case c_floatTag:
|
|
||||||
value = ParseFloat(scalar);
|
|
||||||
break;
|
|
||||||
case c_integerTag:
|
|
||||||
value = ParseInteger(scalar);
|
|
||||||
break;
|
|
||||||
case c_nullTag:
|
|
||||||
value = ParseNull(scalar);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException($"Unexpected tag '{scalar.Tag}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plain style, determine type using YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
if (scalar.Style == ScalarStyle.Plain)
|
|
||||||
{
|
|
||||||
if (MatchNull(scalar, out var nullToken))
|
|
||||||
{
|
|
||||||
value = nullToken;
|
|
||||||
}
|
|
||||||
else if (MatchBoolean(scalar, out var booleanToken))
|
|
||||||
{
|
|
||||||
value = booleanToken;
|
|
||||||
}
|
|
||||||
else if (MatchInteger(scalar, out var numberToken) ||
|
|
||||||
MatchFloat(scalar, out numberToken))
|
|
||||||
{
|
|
||||||
value = numberToken;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise assume string
|
|
||||||
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowSequenceStart(out SequenceToken value)
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is SequenceStart sequenceStart)
|
|
||||||
{
|
|
||||||
value = new SequenceToken(m_fileId, sequenceStart.Start.Line, sequenceStart.Start.Column);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowSequenceEnd()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is SequenceEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowMappingStart(out MappingToken value)
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is MappingStart mappingStart)
|
|
||||||
{
|
|
||||||
value = new MappingToken(m_fileId, mappingStart.Start.Line, mappingStart.Start.Column);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowMappingEnd()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is MappingEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Consumes the last parsing events, which are expected to be DocumentEnd and StreamEnd.
|
|
||||||
/// </summary>
|
|
||||||
public void ValidateEnd()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is DocumentEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected document end parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EvaluateCurrent() is StreamEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected stream end parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MoveNext())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected end of parse events");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Consumes the first parsing events, which are expected to be StreamStart and DocumentStart.
|
|
||||||
/// </summary>
|
|
||||||
public void ValidateStart()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Unexpected parser state");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MoveNext())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected a parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EvaluateCurrent() is StreamStart)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected stream start parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EvaluateCurrent() is DocumentStart)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected document start parse event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ParsingEvent EvaluateCurrent()
|
|
||||||
{
|
|
||||||
if (m_current == null)
|
|
||||||
{
|
|
||||||
m_current = m_parser.Current;
|
|
||||||
if (m_current != null)
|
|
||||||
{
|
|
||||||
if (m_current is Scalar scalar)
|
|
||||||
{
|
|
||||||
// Verify not using achors
|
|
||||||
if (scalar.Anchor != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{scalar.Anchor}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (m_current is MappingStart mappingStart)
|
|
||||||
{
|
|
||||||
// Verify not using achors
|
|
||||||
if (mappingStart.Anchor != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{mappingStart.Anchor}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (m_current is SequenceStart sequenceStart)
|
|
||||||
{
|
|
||||||
// Verify not using achors
|
|
||||||
if (sequenceStart.Anchor != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{sequenceStart.Anchor}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!(m_current is MappingEnd) &&
|
|
||||||
!(m_current is SequenceEnd) &&
|
|
||||||
!(m_current is DocumentStart) &&
|
|
||||||
!(m_current is DocumentEnd) &&
|
|
||||||
!(m_current is StreamStart) &&
|
|
||||||
!(m_current is StreamEnd))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpected parsing event type: {m_current.GetType().Name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_current;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MoveNext()
|
|
||||||
{
|
|
||||||
m_current = null;
|
|
||||||
return m_parser.MoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BooleanToken ParseBoolean(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchBoolean(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_booleanTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NumberToken ParseFloat(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchFloat(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_floatTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NumberToken ParseInteger(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchInteger(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NullToken ParseNull(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchNull(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_nullTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchBoolean(
|
|
||||||
Scalar scalar,
|
|
||||||
out BooleanToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
switch (scalar.Value ?? string.Empty)
|
|
||||||
{
|
|
||||||
case "true":
|
|
||||||
case "True":
|
|
||||||
case "TRUE":
|
|
||||||
value = new BooleanToken(m_fileId, scalar.Start.Line, scalar.Start.Column, true);
|
|
||||||
return true;
|
|
||||||
case "false":
|
|
||||||
case "False":
|
|
||||||
case "FALSE":
|
|
||||||
value = new BooleanToken(m_fileId, scalar.Start.Line, scalar.Start.Column, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchFloat(
|
|
||||||
Scalar scalar,
|
|
||||||
out NumberToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
var str = scalar.Value;
|
|
||||||
if (!string.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
// Check for [-+]?(\.inf|\.Inf|\.INF)|\.nan|\.NaN|\.NAN
|
|
||||||
switch (str)
|
|
||||||
{
|
|
||||||
case ".inf":
|
|
||||||
case ".Inf":
|
|
||||||
case ".INF":
|
|
||||||
case "+.inf":
|
|
||||||
case "+.Inf":
|
|
||||||
case "+.INF":
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.PositiveInfinity);
|
|
||||||
return true;
|
|
||||||
case "-.inf":
|
|
||||||
case "-.Inf":
|
|
||||||
case "-.INF":
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.NegativeInfinity);
|
|
||||||
return true;
|
|
||||||
case ".nan":
|
|
||||||
case ".NaN":
|
|
||||||
case ".NAN":
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.NaN);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise check [-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?
|
|
||||||
|
|
||||||
// Skip leading sign
|
|
||||||
var index = str[0] == '-' || str[0] == '+' ? 1 : 0;
|
|
||||||
|
|
||||||
// Check for integer portion
|
|
||||||
var length = str.Length;
|
|
||||||
var hasInteger = false;
|
|
||||||
while (index < length && str[index] >= '0' && str[index] <= '9')
|
|
||||||
{
|
|
||||||
hasInteger = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for decimal point
|
|
||||||
var hasDot = false;
|
|
||||||
if (index < length && str[index] == '.')
|
|
||||||
{
|
|
||||||
hasDot = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for decimal portion
|
|
||||||
var hasDecimal = false;
|
|
||||||
while (index < length && str[index] >= '0' && str[index] <= '9')
|
|
||||||
{
|
|
||||||
hasDecimal = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check [-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)
|
|
||||||
if ((hasDot && hasDecimal) || hasInteger)
|
|
||||||
{
|
|
||||||
// Check for end
|
|
||||||
if (index == length)
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise exceeds range
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThrowInvalidValue(scalar, c_floatTag); // throws
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check [eE][-+]?[0-9]
|
|
||||||
else if (index < length && (str[index] == 'e' || str[index] == 'E'))
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
|
|
||||||
// Skip sign
|
|
||||||
if (index < length && (str[index] == '-' || str[index] == '+'))
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for exponent
|
|
||||||
var hasExponent = false;
|
|
||||||
while (index < length && str[index] >= '0' && str[index] <= '9')
|
|
||||||
{
|
|
||||||
hasExponent = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for end
|
|
||||||
if (hasExponent && index == length)
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, (Double)doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise exceeds range
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThrowInvalidValue(scalar, c_floatTag); // throws
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchInteger(
|
|
||||||
Scalar scalar,
|
|
||||||
out NumberToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
var str = scalar.Value;
|
|
||||||
if (!string.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
// Check for [0-9]+
|
|
||||||
var firstChar = str[0];
|
|
||||||
if (firstChar >= '0' && firstChar <= '9' &&
|
|
||||||
str.Skip(1).All(x => x >= '0' && x <= '9'))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.None, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise exceeds range
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
// Check for (-|+)[0-9]+
|
|
||||||
else if ((firstChar == '-' || firstChar == '+') &&
|
|
||||||
str.Length > 1 &&
|
|
||||||
str.Skip(1).All(x => x >= '0' && x <= '9'))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise exceeds range
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
// Check for 0x[0-9a-fA-F]+
|
|
||||||
else if (firstChar == '0' &&
|
|
||||||
str.Length > 2 &&
|
|
||||||
str[1] == 'x' &&
|
|
||||||
str.Skip(2).All(x => (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Int32.TryParse(str.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var integerValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, integerValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise exceeds range
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
// Check for 0o[0-9]+
|
|
||||||
else if (firstChar == '0' &&
|
|
||||||
str.Length > 2 &&
|
|
||||||
str[1] == 'o' &&
|
|
||||||
str.Skip(2).All(x => x >= '0' && x <= '7'))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
var integerValue = default(Int32);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
integerValue = Convert.ToInt32(str.Substring(2), 8);
|
|
||||||
}
|
|
||||||
// Otherwise exceeds range
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, integerValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchNull(
|
|
||||||
Scalar scalar,
|
|
||||||
out NullToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
switch (scalar.Value ?? string.Empty)
|
|
||||||
{
|
|
||||||
case "":
|
|
||||||
case "null":
|
|
||||||
case "Null":
|
|
||||||
case "NULL":
|
|
||||||
case "~":
|
|
||||||
value = new NullToken(m_fileId, scalar.Start.Line, scalar.Start.Column);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThrowInvalidValue(
|
|
||||||
Scalar scalar,
|
|
||||||
String tag)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"The value '{scalar.Value}' on line {scalar.Start.Line} and column {scalar.Start.Column} is invalid for the type '{scalar.Tag}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
private const String c_booleanTag = "tag:yaml.org,2002:bool";
|
|
||||||
private const String c_floatTag = "tag:yaml.org,2002:float";
|
|
||||||
private const String c_integerTag = "tag:yaml.org,2002:int";
|
|
||||||
private const String c_nullTag = "tag:yaml.org,2002:null";
|
|
||||||
private const String c_stringTag = "tag:yaml.org,2002:string";
|
|
||||||
private readonly Int32? m_fileId;
|
|
||||||
private readonly Parser m_parser;
|
|
||||||
private ParsingEvent m_current;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public enum ActionRunStage
|
public enum ActionRunStage
|
||||||
{
|
{
|
||||||
Pre,
|
|
||||||
Main,
|
Main,
|
||||||
Post,
|
Post,
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public interface IActionRunner : IStep, IRunnerService
|
public interface IActionRunner : IStep, IRunnerService
|
||||||
{
|
{
|
||||||
ActionRunStage Stage { get; set; }
|
ActionRunStage Stage { get; set; }
|
||||||
Boolean TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
|
bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
|
||||||
Pipelines.ActionStep Action { get; set; }
|
Pipelines.ActionStep Action { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,18 +81,20 @@ namespace GitHub.Runner.Worker
|
|||||||
ActionExecutionData handlerData = definition.Data?.Execution;
|
ActionExecutionData handlerData = definition.Data?.Execution;
|
||||||
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
||||||
|
|
||||||
if (handlerData.HasInit &&
|
|
||||||
Action.Reference is Pipelines.RepositoryPathReference repoAction &&
|
|
||||||
string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ExecutionContext.Warning($"`pre` execution is not supported for local action from '{repoAction.Path}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// The action has post cleanup defined.
|
// The action has post cleanup defined.
|
||||||
// we need to create timeline record for them and add them to the step list that StepRunner is using
|
// we need to create timeline record for them and add them to the step list that StepRunner is using
|
||||||
if (handlerData.HasCleanup && (Stage == ActionRunStage.Pre || Stage == ActionRunStage.Main))
|
if (handlerData.HasCleanup && Stage == ActionRunStage.Main)
|
||||||
{
|
{
|
||||||
string postDisplayName = $"Post {this.DisplayName}";
|
string postDisplayName = null;
|
||||||
|
if (this.DisplayName.StartsWith(PipelineTemplateConstants.RunDisplayPrefix))
|
||||||
|
{
|
||||||
|
postDisplayName = $"Post {this.DisplayName.Substring(PipelineTemplateConstants.RunDisplayPrefix.Length)}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
postDisplayName = $"Post {this.DisplayName}";
|
||||||
|
}
|
||||||
|
|
||||||
var repositoryReference = Action.Reference as RepositoryPathReference;
|
var repositoryReference = Action.Reference as RepositoryPathReference;
|
||||||
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
|
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
|
||||||
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
||||||
@@ -107,7 +108,7 @@ namespace GitHub.Runner.Worker
|
|||||||
actionRunner.Condition = handlerData.CleanupCondition;
|
actionRunner.Condition = handlerData.CleanupCondition;
|
||||||
actionRunner.DisplayName = postDisplayName;
|
actionRunner.DisplayName = postDisplayName;
|
||||||
|
|
||||||
ExecutionContext.RegisterPostJobStep(actionRunner);
|
ExecutionContext.RegisterPostJobStep($"{actionRunner.Action.Name}_post", actionRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
||||||
@@ -140,10 +141,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Load the inputs.
|
// Load the inputs.
|
||||||
ExecutionContext.Debug("Loading inputs");
|
ExecutionContext.Debug("Loading inputs");
|
||||||
var templateTrace = ExecutionContext.ToTemplateTraceWriter();
|
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema);
|
|
||||||
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues);
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, string> input in inputs)
|
foreach (KeyValuePair<string, string> input in inputs)
|
||||||
{
|
{
|
||||||
@@ -163,13 +162,7 @@ namespace GitHub.Runner.Worker
|
|||||||
string key = input.Key.AssertString("action input name").Value;
|
string key = input.Key.AssertString("action input name").Value;
|
||||||
if (!inputs.ContainsKey(key))
|
if (!inputs.ContainsKey(key))
|
||||||
{
|
{
|
||||||
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value);
|
||||||
foreach (var data in ExecutionContext.ExpressionValues)
|
|
||||||
{
|
|
||||||
evaluateContext[data.Key] = data.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value, evaluateContext);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,11 +287,14 @@ namespace GitHub.Runner.Worker
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
// Try evaluating fully
|
// Try evaluating fully
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(context.ToTemplateTraceWriter(), schema);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
didFullyEvaluate = templateEvaluator.TryEvaluateStepDisplayName(tokenToParse, contextData, out displayName);
|
if (tokenToParse.CheckHasRequiredContext(contextData, context.ExpressionFunctions))
|
||||||
|
{
|
||||||
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
|
displayName = templateEvaluator.EvaluateStepDisplayName(tokenToParse, contextData, context.ExpressionFunctions);
|
||||||
|
didFullyEvaluate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (TemplateValidationException e)
|
catch (TemplateValidationException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
foreach (var volume in container.Volumes)
|
foreach (var volume in container.Volumes)
|
||||||
{
|
{
|
||||||
UserMountVolumes[volume] = volume;
|
UserMountVolumes[volume] = volume;
|
||||||
|
MountVolumes.Add(new MountVolume(volume));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,13 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
// Watermark for GitHub Action environment
|
// Watermark for GitHub Action environment
|
||||||
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
||||||
|
|
||||||
|
// Set CI=true when no one else already set it.
|
||||||
|
// CI=true is common set in most CI provider in GitHub
|
||||||
|
if (!container.ContainerEnvironmentVariables.ContainsKey("CI"))
|
||||||
|
{
|
||||||
|
dockerOptions.Add("-e CI=true");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var volume in container.MountVolumes)
|
foreach (var volume in container.MountVolumes)
|
||||||
{
|
{
|
||||||
// replace `"` with `\"` and add `"{0}"` to all path.
|
// replace `"` with `\"` and add `"{0}"` to all path.
|
||||||
@@ -189,6 +196,13 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
// Watermark for GitHub Action environment
|
// Watermark for GitHub Action environment
|
||||||
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
||||||
|
|
||||||
|
// Set CI=true when no one else already set it.
|
||||||
|
// CI=true is common set in most CI provider in GitHub
|
||||||
|
if (!container.ContainerEnvironmentVariables.ContainsKey("CI"))
|
||||||
|
{
|
||||||
|
dockerOptions.Add("-e CI=true");
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(container.ContainerEntryPoint))
|
if (!string.IsNullOrEmpty(container.ContainerEntryPoint))
|
||||||
{
|
{
|
||||||
dockerOptions.Add($"--entrypoint \"{container.ContainerEntryPoint}\"");
|
dockerOptions.Add($"--entrypoint \"{container.ContainerEntryPoint}\"");
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ namespace GitHub.Runner.Worker
|
|||||||
data: data);
|
data: data);
|
||||||
|
|
||||||
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
|
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
|
||||||
executionContext.RegisterPostJobStep(postJobStep);
|
executionContext.RegisterPostJobStep(nameof(StopContainersAsync), postJobStep);
|
||||||
|
|
||||||
// Check whether we are inside a container.
|
// Check whether we are inside a container.
|
||||||
// Our container feature requires to map working directory from host to the container.
|
// Our container feature requires to map working directory from host to the container.
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker.Container;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Text;
|
|
||||||
using System.Collections;
|
|
||||||
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -39,6 +39,7 @@ namespace GitHub.Runner.Worker
|
|||||||
string ContextName { get; }
|
string ContextName { get; }
|
||||||
Task ForceCompleted { get; }
|
Task ForceCompleted { get; }
|
||||||
TaskResult? Result { get; set; }
|
TaskResult? Result { get; set; }
|
||||||
|
TaskResult? Outcome { get; set; }
|
||||||
string ResultCode { get; set; }
|
string ResultCode { get; set; }
|
||||||
TaskResult? CommandResult { get; set; }
|
TaskResult? CommandResult { get; set; }
|
||||||
CancellationToken CancellationToken { get; }
|
CancellationToken CancellationToken { get; }
|
||||||
@@ -47,11 +48,14 @@ namespace GitHub.Runner.Worker
|
|||||||
PlanFeatures Features { get; }
|
PlanFeatures Features { get; }
|
||||||
Variables Variables { get; }
|
Variables Variables { get; }
|
||||||
Dictionary<string, string> IntraActionState { get; }
|
Dictionary<string, string> IntraActionState { get; }
|
||||||
HashSet<string> OutputVariables { get; }
|
IDictionary<String, IDictionary<String, String>> JobDefaults { get; }
|
||||||
|
Dictionary<string, VariableValue> JobOutputs { get; }
|
||||||
IDictionary<String, String> EnvironmentVariables { get; }
|
IDictionary<String, String> EnvironmentVariables { get; }
|
||||||
IDictionary<String, ContextScope> Scopes { get; }
|
IDictionary<String, ContextScope> Scopes { get; }
|
||||||
|
IList<String> FileTable { get; }
|
||||||
StepsContext StepsContext { get; }
|
StepsContext StepsContext { get; }
|
||||||
DictionaryContextData ExpressionValues { get; }
|
DictionaryContextData ExpressionValues { get; }
|
||||||
|
IList<IFunctionInfo> ExpressionFunctions { get; }
|
||||||
List<string> PrependPath { get; }
|
List<string> PrependPath { get; }
|
||||||
ContainerInfo Container { get; set; }
|
ContainerInfo Container { get; set; }
|
||||||
List<ContainerInfo> ServiceContainers { get; }
|
List<ContainerInfo> ServiceContainers { get; }
|
||||||
@@ -99,9 +103,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// others
|
// others
|
||||||
void ForceTaskComplete();
|
void ForceTaskComplete();
|
||||||
void RegisterPostJobStep(IStep step);
|
void RegisterPostJobStep(string refName, IStep step);
|
||||||
Task<string> GetGitHubToken();
|
|
||||||
Task UpdateGitHubTokenInContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
@@ -111,9 +113,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private readonly TimelineRecord _record = new TimelineRecord();
|
private readonly TimelineRecord _record = new TimelineRecord();
|
||||||
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
|
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
|
||||||
private readonly object _loggerLock = new object();
|
private readonly object _loggerLock = new object();
|
||||||
private readonly HashSet<string> _outputvariables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
private readonly object _matchersLock = new object();
|
private readonly object _matchersLock = new object();
|
||||||
public readonly List<Task> _getGitHubTokenTasks = new List<Task>();
|
|
||||||
|
|
||||||
private event OnMatcherChanged _onMatcherChanged;
|
private event OnMatcherChanged _onMatcherChanged;
|
||||||
|
|
||||||
@@ -134,13 +134,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// only job level ExecutionContext will track throttling delay.
|
// only job level ExecutionContext will track throttling delay.
|
||||||
private long _totalThrottlingDelayInMilliseconds = 0;
|
private long _totalThrottlingDelayInMilliseconds = 0;
|
||||||
|
|
||||||
private Guid _jobId;
|
|
||||||
private Guid _scopeIdentifier;
|
|
||||||
private string _hubName;
|
|
||||||
private Guid _planId;
|
|
||||||
|
|
||||||
private Stopwatch _githubTokenExpireTimer = new Stopwatch();
|
|
||||||
|
|
||||||
public Guid Id => _record.Id;
|
public Guid Id => _record.Id;
|
||||||
public string ScopeName { get; private set; }
|
public string ScopeName { get; private set; }
|
||||||
public string ContextName { get; private set; }
|
public string ContextName { get; private set; }
|
||||||
@@ -149,11 +142,14 @@ namespace GitHub.Runner.Worker
|
|||||||
public List<ServiceEndpoint> Endpoints { get; private set; }
|
public List<ServiceEndpoint> Endpoints { get; private set; }
|
||||||
public Variables Variables { get; private set; }
|
public Variables Variables { get; private set; }
|
||||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||||
public HashSet<string> OutputVariables => _outputvariables;
|
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
||||||
|
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
||||||
public IDictionary<String, String> EnvironmentVariables { get; private set; }
|
public IDictionary<String, String> EnvironmentVariables { get; private set; }
|
||||||
public IDictionary<String, ContextScope> Scopes { get; private set; }
|
public IDictionary<String, ContextScope> Scopes { get; private set; }
|
||||||
|
public IList<String> FileTable { get; private set; }
|
||||||
public StepsContext StepsContext { get; private set; }
|
public StepsContext StepsContext { get; private set; }
|
||||||
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
||||||
|
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
||||||
public bool WriteDebug { get; private set; }
|
public bool WriteDebug { get; private set; }
|
||||||
public List<string> PrependPath { get; private set; }
|
public List<string> PrependPath { get; private set; }
|
||||||
public ContainerInfo Container { get; set; }
|
public ContainerInfo Container { get; set; }
|
||||||
@@ -165,9 +161,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// Only job level ExecutionContext has PostJobSteps
|
// Only job level ExecutionContext has PostJobSteps
|
||||||
public Stack<IStep> PostJobSteps { get; private set; }
|
public Stack<IStep> PostJobSteps { get; private set; }
|
||||||
|
|
||||||
// Only job level ExecutionContext has StepsWithPostRegisted
|
|
||||||
public HashSet<Guid> StepsWithPostRegisted { get; private set; }
|
|
||||||
|
|
||||||
public bool EchoOnActionCommand { get; set; }
|
public bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
|
|
||||||
@@ -183,6 +176,8 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TaskResult? Outcome { get; set; }
|
||||||
|
|
||||||
public TaskResult? CommandResult { get; set; }
|
public TaskResult? CommandResult { get; set; }
|
||||||
|
|
||||||
private string ContextType => _record.RecordType;
|
private string ContextType => _record.RecordType;
|
||||||
@@ -253,68 +248,9 @@ namespace GitHub.Runner.Worker
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetGitHubToken()
|
public void RegisterPostJobStep(string refName, IStep step)
|
||||||
{
|
{
|
||||||
Trace.Info($"Try get new GITHUB_TOKEN");
|
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, refName, IntraActionState);
|
||||||
return Root.RefreshGitHubToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateGitHubTokenInContext()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Root.EnsureGitHubToken();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.Warning($"Fail to get a new GITHUB_TOKEN, error: {ex.Message}");
|
|
||||||
this.Debug(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> RefreshGitHubToken()
|
|
||||||
{
|
|
||||||
Trace.Info("Request new GITHUB_TOKEN");
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
|
||||||
var githubToken = await jobServer.RefreshGitHubTokenAsync(_scopeIdentifier, _hubName, _planId, _jobId, CancellationToken.None);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(githubToken?.Token))
|
|
||||||
{
|
|
||||||
// register secret masker
|
|
||||||
Trace.Info("Register secret masker for new GITHUB_TOKEN");
|
|
||||||
HostContext.SecretMasker.AddValue(githubToken.Token);
|
|
||||||
return githubToken.Token;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Get empty GTIHUB_TOKEN.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EnsureGitHubToken()
|
|
||||||
{
|
|
||||||
// needs to refresh GITHUB_TOKEN every 50 mins since the token is good for 60 min by default
|
|
||||||
if (_githubTokenExpireTimer.Elapsed.TotalMilliseconds > 10)
|
|
||||||
{
|
|
||||||
var githubToken = await this.RefreshGitHubToken();
|
|
||||||
var secretsContext = ExpressionValues["secrets"] as DictionaryContextData;
|
|
||||||
secretsContext["GITHUB_TOKEN"] = new StringContextData(githubToken);
|
|
||||||
var githubContext = ExpressionValues["github"] as GitHubContext;
|
|
||||||
githubContext["token"] = new StringContextData(githubToken);
|
|
||||||
|
|
||||||
_githubTokenExpireTimer.Restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterPostJobStep(IStep step)
|
|
||||||
{
|
|
||||||
if (step is IActionRunner actionRunner && !Root.StepsWithPostRegisted.Add(actionRunner.Action.Id))
|
|
||||||
{
|
|
||||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, ScopeName, ContextName, IntraActionState);
|
|
||||||
Root.PostJobSteps.Push(step);
|
Root.PostJobSteps.Push(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,12 +274,18 @@ namespace GitHub.Runner.Worker
|
|||||||
child.IntraActionState = intraActionState;
|
child.IntraActionState = intraActionState;
|
||||||
}
|
}
|
||||||
child.EnvironmentVariables = EnvironmentVariables;
|
child.EnvironmentVariables = EnvironmentVariables;
|
||||||
|
child.JobDefaults = JobDefaults;
|
||||||
child.Scopes = Scopes;
|
child.Scopes = Scopes;
|
||||||
|
child.FileTable = FileTable;
|
||||||
child.StepsContext = StepsContext;
|
child.StepsContext = StepsContext;
|
||||||
foreach (var pair in ExpressionValues)
|
foreach (var pair in ExpressionValues)
|
||||||
{
|
{
|
||||||
child.ExpressionValues[pair.Key] = pair.Value;
|
child.ExpressionValues[pair.Key] = pair.Value;
|
||||||
}
|
}
|
||||||
|
foreach (var item in ExpressionFunctions)
|
||||||
|
{
|
||||||
|
child.ExpressionFunctions.Add(item);
|
||||||
|
}
|
||||||
child._cancellationTokenSource = new CancellationTokenSource();
|
child._cancellationTokenSource = new CancellationTokenSource();
|
||||||
child.WriteDebug = WriteDebug;
|
child.WriteDebug = WriteDebug;
|
||||||
child._parentExecutionContext = this;
|
child._parentExecutionContext = this;
|
||||||
@@ -416,6 +358,12 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
_logger.End();
|
_logger.End();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(ContextName))
|
||||||
|
{
|
||||||
|
StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult().ToString());
|
||||||
|
StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
return Result.Value;
|
return Result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,6 +574,12 @@ namespace GitHub.Runner.Worker
|
|||||||
// Environment variables shared across all actions
|
// Environment variables shared across all actions
|
||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
|
||||||
|
// Job defaults shared across all actions
|
||||||
|
JobDefaults = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Job Outputs
|
||||||
|
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Service container info
|
// Service container info
|
||||||
ServiceContainers = new List<ContainerInfo>();
|
ServiceContainers = new List<ContainerInfo>();
|
||||||
|
|
||||||
@@ -642,11 +596,8 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expression functions
|
// File table
|
||||||
if (Variables.GetBoolean("System.HashFilesV2") == true)
|
FileTable = new List<String>(message.FileTable ?? new string[0]);
|
||||||
{
|
|
||||||
ExpressionConstants.UpdateFunction<Handlers.HashFiles>("hashFiles", 1, byte.MaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression values
|
// Expression values
|
||||||
if (message.ContextData?.Count > 0)
|
if (message.ContextData?.Count > 0)
|
||||||
@@ -665,8 +616,13 @@ namespace GitHub.Runner.Worker
|
|||||||
var githubAccessToken = new StringContextData(Variables.Get("system.github.token"));
|
var githubAccessToken = new StringContextData(Variables.Get("system.github.token"));
|
||||||
var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}"));
|
var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}"));
|
||||||
HostContext.SecretMasker.AddValue(base64EncodedToken);
|
HostContext.SecretMasker.AddValue(base64EncodedToken);
|
||||||
|
var githubJob = Variables.Get("system.github.job");
|
||||||
var githubContext = new GitHubContext();
|
var githubContext = new GitHubContext();
|
||||||
githubContext["token"] = githubAccessToken;
|
githubContext["token"] = githubAccessToken;
|
||||||
|
if (!string.IsNullOrEmpty(githubJob))
|
||||||
|
{
|
||||||
|
githubContext["job"] = new StringContextData(githubJob);
|
||||||
|
}
|
||||||
var githubDictionary = ExpressionValues["github"].AssertDictionary("github");
|
var githubDictionary = ExpressionValues["github"].AssertDictionary("github");
|
||||||
foreach (var pair in githubDictionary)
|
foreach (var pair in githubDictionary)
|
||||||
{
|
{
|
||||||
@@ -681,7 +637,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
ExpressionValues["env"] = new CaseSensitiveDictionaryContextData();
|
ExpressionValues["env"] = new CaseSensitiveDictionaryContextData();
|
||||||
#endif
|
#endif
|
||||||
_githubTokenExpireTimer.Start();
|
|
||||||
|
|
||||||
// Prepend Path
|
// Prepend Path
|
||||||
PrependPath = new List<string>();
|
PrependPath = new List<string>();
|
||||||
@@ -692,14 +647,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// PostJobSteps for job ExecutionContext
|
// PostJobSteps for job ExecutionContext
|
||||||
PostJobSteps = new Stack<IStep>();
|
PostJobSteps = new Stack<IStep>();
|
||||||
|
|
||||||
// StepsWithPostRegisted for job ExecutionContext
|
|
||||||
StepsWithPostRegisted = new HashSet<Guid>();
|
|
||||||
|
|
||||||
_scopeIdentifier = message.Plan.ScopeIdentifier;
|
|
||||||
_hubName = message.Plan.PlanType;
|
|
||||||
_jobId = message.JobId;
|
|
||||||
_planId = message.Plan.PlanId;
|
|
||||||
|
|
||||||
// Job timeline record.
|
// Job timeline record.
|
||||||
InitializeTimelineRecord(
|
InitializeTimelineRecord(
|
||||||
timelineId: message.Timeline.Id,
|
timelineId: message.Timeline.Id,
|
||||||
@@ -811,7 +758,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var owners = config.Matchers.Select(x => $"'{x.Owner}'");
|
var owners = config.Matchers.Select(x => $"'{x.Owner}'");
|
||||||
var joinedOwners = string.Join(", ", owners);
|
var joinedOwners = string.Join(", ", owners);
|
||||||
// todo: loc
|
// todo: loc
|
||||||
this.Output($"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline.");
|
this.Debug($"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,7 +800,7 @@ namespace GitHub.Runner.Worker
|
|||||||
owners = removedMatchers.Select(x => $"'{x.Owner}'");
|
owners = removedMatchers.Select(x => $"'{x.Owner}'");
|
||||||
var joinedOwners = string.Join(", ", owners);
|
var joinedOwners = string.Join(", ", owners);
|
||||||
// todo: loc
|
// todo: loc
|
||||||
this.Output($"Removed matchers: {joinedOwners}");
|
this.Debug($"Removed matchers: {joinedOwners}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +847,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IExecutionContext CreatePostChild(string displayName, string scopeName, string contextName, Dictionary<string, string> intraActionState)
|
private IExecutionContext CreatePostChild(string displayName, string refName, Dictionary<string, string> intraActionState)
|
||||||
{
|
{
|
||||||
if (!_expandedForPostJob)
|
if (!_expandedForPostJob)
|
||||||
{
|
{
|
||||||
@@ -909,8 +856,7 @@ namespace GitHub.Runner.Worker
|
|||||||
_childTimelineRecordOrder = _childTimelineRecordOrder * 2;
|
_childTimelineRecordOrder = _childTimelineRecordOrder * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newGuid = Guid.NewGuid();
|
return CreateChild(Guid.NewGuid(), displayName, refName, null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
|
||||||
return CreateChild(newGuid, displayName, newGuid.ToString("N"), scopeName, contextName, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,6 +915,21 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<KeyValuePair<string, object>> ToExpressionState(this IExecutionContext context)
|
||||||
|
{
|
||||||
|
return new[] { new KeyValuePair<string, object>(nameof(IExecutionContext), context) };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
|
||||||
|
{
|
||||||
|
if (traceWriter == null)
|
||||||
|
{
|
||||||
|
traceWriter = context.ToTemplateTraceWriter();
|
||||||
|
}
|
||||||
|
var schema = PipelineTemplateSchemaFactory.GetSchema();
|
||||||
|
return new PipelineTemplateEvaluator(traceWriter, schema, context.FileTable);
|
||||||
|
}
|
||||||
|
|
||||||
public static ObjectTemplating.ITraceWriter ToTemplateTraceWriter(this IExecutionContext context)
|
public static ObjectTemplating.ITraceWriter ToTemplateTraceWriter(this IExecutionContext context)
|
||||||
{
|
{
|
||||||
return new TemplateTraceWriter(context);
|
return new TemplateTraceWriter(context);
|
||||||
@@ -981,6 +942,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
internal TemplateTraceWriter(IExecutionContext executionContext)
|
internal TemplateTraceWriter(IExecutionContext executionContext)
|
||||||
{
|
{
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
_executionContext = executionContext;
|
_executionContext = executionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using GitHub.DistributedTask.Expressions2;
|
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
|
||||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
|
||||||
{
|
|
||||||
[ServiceLocator(Default = typeof(ExpressionManager))]
|
|
||||||
public interface IExpressionManager : IRunnerService
|
|
||||||
{
|
|
||||||
ConditionResult Evaluate(IExecutionContext context, string condition, bool hostTracingOnly = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ExpressionManager : RunnerService, IExpressionManager
|
|
||||||
{
|
|
||||||
public ConditionResult Evaluate(IExecutionContext executionContext, string condition, bool hostTracingOnly = false)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
|
|
||||||
ConditionResult result = new ConditionResult();
|
|
||||||
var expressionTrace = new TraceWriter(Trace, hostTracingOnly ? null : executionContext);
|
|
||||||
var tree = Parse(executionContext, expressionTrace, condition);
|
|
||||||
var expressionResult = tree.Evaluate(expressionTrace, HostContext.SecretMasker, state: executionContext, options: null);
|
|
||||||
result.Value = expressionResult.IsTruthy;
|
|
||||||
result.Trace = expressionTrace.Trace;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IExpressionNode Parse(IExecutionContext executionContext, TraceWriter expressionTrace, string condition)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(condition))
|
|
||||||
{
|
|
||||||
condition = $"{PipelineTemplateConstants.Success}()";
|
|
||||||
}
|
|
||||||
|
|
||||||
var parser = new ExpressionParser();
|
|
||||||
var namedValues = executionContext.ExpressionValues.Keys.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
|
||||||
var functions = new IFunctionInfo[]
|
|
||||||
{
|
|
||||||
new FunctionInfo<AlwaysNode>(name: Constants.Expressions.Always, minParameters: 0, maxParameters: 0),
|
|
||||||
new FunctionInfo<CancelledNode>(name: Constants.Expressions.Cancelled, minParameters: 0, maxParameters: 0),
|
|
||||||
new FunctionInfo<FailureNode>(name: Constants.Expressions.Failure, minParameters: 0, maxParameters: 0),
|
|
||||||
new FunctionInfo<SuccessNode>(name: Constants.Expressions.Success, minParameters: 0, maxParameters: 0),
|
|
||||||
};
|
|
||||||
return parser.CreateTree(condition, expressionTrace, namedValues, functions) ?? new SuccessNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class TraceWriter : DistributedTask.Expressions2.ITraceWriter
|
|
||||||
{
|
|
||||||
private readonly IExecutionContext _executionContext;
|
|
||||||
private readonly Tracing _trace;
|
|
||||||
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
public string Trace => _traceBuilder.ToString();
|
|
||||||
|
|
||||||
public TraceWriter(Tracing trace, IExecutionContext executionContext)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(trace, nameof(trace));
|
|
||||||
_trace = trace;
|
|
||||||
_executionContext = executionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Info(string message)
|
|
||||||
{
|
|
||||||
_trace.Info(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
_traceBuilder.AppendLine(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(string message)
|
|
||||||
{
|
|
||||||
_trace.Verbose(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class AlwaysNode : Function
|
|
||||||
{
|
|
||||||
protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class CancelledNode : Function
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var executionContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
|
||||||
return jobStatus == ActionResult.Cancelled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class FailureNode : Function
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var executionContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
|
||||||
return jobStatus == ActionResult.Failure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class SuccessNode : Function
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var executionContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
|
||||||
return jobStatus == ActionResult.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ContextValueNode : NamedValue
|
|
||||||
{
|
|
||||||
protected override Object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var jobContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(jobContext, nameof(jobContext));
|
|
||||||
return jobContext.ExpressionValues[Name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConditionResult
|
|
||||||
{
|
|
||||||
public ConditionResult(bool value = false, string trace = null)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
this.Trace = trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Value { get; set; }
|
|
||||||
public string Trace { get; set; }
|
|
||||||
|
|
||||||
public static implicit operator ConditionResult(bool value)
|
|
||||||
{
|
|
||||||
return new ConditionResult(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
src/Runner.Worker/Expressions/AlwaysFunction.cs
Normal file
25
src/Runner.Worker/Expressions/AlwaysFunction.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class AlwaysFunction : Function
|
||||||
|
{
|
||||||
|
protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Runner.Worker/Expressions/CancelledFunction.cs
Normal file
31
src/Runner.Worker/Expressions/CancelledFunction.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class CancelledFunction : Function
|
||||||
|
{
|
||||||
|
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var templateContext = evaluationContext.State as TemplateContext;
|
||||||
|
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||||
|
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||||
|
return jobStatus == ActionResult.Cancelled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Runner.Worker/Expressions/FailureFunction.cs
Normal file
31
src/Runner.Worker/Expressions/FailureFunction.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class FailureFunction : Function
|
||||||
|
{
|
||||||
|
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var templateContext = evaluationContext.State as TemplateContext;
|
||||||
|
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||||
|
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||||
|
return jobStatus == ActionResult.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,28 +8,9 @@ using System.Reflection;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker.Handlers
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
{
|
{
|
||||||
public class FunctionTrace : ITraceWriter
|
public sealed class HashFilesFunction : Function
|
||||||
{
|
|
||||||
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
|
|
||||||
|
|
||||||
public FunctionTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
|
|
||||||
{
|
|
||||||
_trace = trace;
|
|
||||||
}
|
|
||||||
public void Info(string message)
|
|
||||||
{
|
|
||||||
_trace.Info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(string message)
|
|
||||||
{
|
|
||||||
_trace.Info(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class HashFiles : Function
|
|
||||||
{
|
{
|
||||||
protected sealed override Object EvaluateCore(
|
protected sealed override Object EvaluateCore(
|
||||||
EvaluationContext context,
|
EvaluationContext context,
|
||||||
@@ -82,7 +63,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}");
|
string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}");
|
||||||
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
||||||
var hashResult = string.Empty;
|
var hashResult = string.Empty;
|
||||||
var p = new ProcessInvoker(new FunctionTrace(context.Trace));
|
var p = new ProcessInvoker(new HashFilesTrace(context.Trace));
|
||||||
p.ErrorDataReceived += ((_, data) =>
|
p.ErrorDataReceived += ((_, data) =>
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
|
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
|
||||||
@@ -122,5 +103,24 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
return hashResult;
|
return hashResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class HashFilesTrace : ITraceWriter
|
||||||
|
{
|
||||||
|
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
|
||||||
|
|
||||||
|
public HashFilesTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
|
||||||
|
{
|
||||||
|
_trace = trace;
|
||||||
|
}
|
||||||
|
public void Info(string message)
|
||||||
|
{
|
||||||
|
_trace.Info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string message)
|
||||||
|
{
|
||||||
|
_trace.Info(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
31
src/Runner.Worker/Expressions/SuccessFunction.cs
Normal file
31
src/Runner.Worker/Expressions/SuccessFunction.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class SuccessFunction : Function
|
||||||
|
{
|
||||||
|
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var templateContext = evaluationContext.State as TemplateContext;
|
||||||
|
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||||
|
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||||
|
return jobStatus == ActionResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,10 @@ namespace GitHub.Runner.Worker
|
|||||||
"event_name",
|
"event_name",
|
||||||
"event_path",
|
"event_path",
|
||||||
"head_ref",
|
"head_ref",
|
||||||
|
"job",
|
||||||
"ref",
|
"ref",
|
||||||
"repository",
|
"repository",
|
||||||
|
"repository_owner",
|
||||||
"run_id",
|
"run_id",
|
||||||
"run_number",
|
"run_number",
|
||||||
"sha",
|
"sha",
|
||||||
|
|||||||
@@ -82,10 +82,6 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
|
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (stage == ActionRunStage.Pre)
|
|
||||||
{
|
|
||||||
container.ContainerEntryPoint = Data.Init;
|
|
||||||
}
|
|
||||||
else if (stage == ActionRunStage.Post)
|
else if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
container.ContainerEntryPoint = Data.Cleanup;
|
container.ContainerEntryPoint = Data.Cleanup;
|
||||||
@@ -101,14 +97,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
var extraExpressionValues = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
||||||
evaluateContext["inputs"] = inputsContext;
|
extraExpressionValues["inputs"] = inputsContext;
|
||||||
|
|
||||||
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
||||||
if (Data.Arguments != null)
|
if (Data.Arguments != null)
|
||||||
{
|
{
|
||||||
container.ContainerEntryPointArgs = "";
|
container.ContainerEntryPointArgs = "";
|
||||||
var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, evaluateContext);
|
var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, extraExpressionValues);
|
||||||
foreach (var arg in evaluatedArgs)
|
foreach (var arg in evaluatedArgs)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(arg))
|
if (!string.IsNullOrEmpty(arg))
|
||||||
@@ -128,7 +124,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
if (Data.Environment != null)
|
if (Data.Environment != null)
|
||||||
{
|
{
|
||||||
var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, evaluateContext);
|
var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, extraExpressionValues);
|
||||||
foreach (var env in evaluatedEnv)
|
foreach (var env in evaluatedEnv)
|
||||||
{
|
{
|
||||||
if (!this.Environment.ContainsKey(env.Key))
|
if (!this.Environment.ContainsKey(env.Key))
|
||||||
|
|||||||
@@ -60,10 +60,6 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
target = Data.Script;
|
target = Data.Script;
|
||||||
}
|
}
|
||||||
else if (stage == ActionRunStage.Pre)
|
|
||||||
{
|
|
||||||
target = Data.Init;
|
|
||||||
}
|
|
||||||
else if (stage == ActionRunStage.Post)
|
else if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
target = Data.Cleanup;
|
target = Data.Cleanup;
|
||||||
|
|||||||
@@ -58,7 +58,16 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
string shellCommandPath = null;
|
string shellCommandPath = null;
|
||||||
bool validateShellOnHost = !(StepHost is ContainerStepHost);
|
bool validateShellOnHost = !(StepHost is ContainerStepHost);
|
||||||
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
||||||
Inputs.TryGetValue("shell", out var shell);
|
string shell = null;
|
||||||
|
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
runDefaults.TryGetValue("shell", out shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (string.IsNullOrEmpty(shell))
|
if (string.IsNullOrEmpty(shell))
|
||||||
{
|
{
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
@@ -139,11 +148,36 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Inputs.TryGetValue("script", out var contents);
|
Inputs.TryGetValue("script", out var contents);
|
||||||
contents = contents ?? string.Empty;
|
contents = contents ?? string.Empty;
|
||||||
|
|
||||||
Inputs.TryGetValue("workingDirectory", out var workingDirectory);
|
string workingDirectory = null;
|
||||||
|
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
if (runDefaults.TryGetValue("working-directory", out workingDirectory))
|
||||||
|
{
|
||||||
|
ExecutionContext.Debug("Overwrite 'working-directory' base on job defaults.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var workspaceDir = githubContext["workspace"] as StringContextData;
|
var workspaceDir = githubContext["workspace"] as StringContextData;
|
||||||
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
|
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
|
||||||
|
|
||||||
Inputs.TryGetValue("shell", out var shell);
|
string shell = null;
|
||||||
|
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
if (runDefaults.TryGetValue("shell", out shell))
|
||||||
|
{
|
||||||
|
ExecutionContext.Debug("Overwrite 'shell' base on job defaults.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var isContainerStepHost = StepHost is ContainerStepHost;
|
var isContainerStepHost = StepHost is ContainerStepHost;
|
||||||
|
|
||||||
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
@@ -14,6 +17,16 @@ using Pipelines = GitHub.DistributedTask.Pipelines;
|
|||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class SetupInfo
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string Group { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string Detail { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(JobExtension))]
|
[ServiceLocator(Default = typeof(JobExtension))]
|
||||||
|
|
||||||
public interface IJobExtension : IRunnerService
|
public interface IJobExtension : IRunnerService
|
||||||
@@ -49,6 +62,44 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Start();
|
context.Start();
|
||||||
context.Debug($"Starting: Set up job");
|
context.Debug($"Starting: Set up job");
|
||||||
context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
|
context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
|
||||||
|
|
||||||
|
var setupInfoFile = HostContext.GetConfigFile(WellKnownConfigFile.SetupInfo);
|
||||||
|
if (File.Exists(setupInfoFile))
|
||||||
|
{
|
||||||
|
Trace.Info($"Load machine setup info from {setupInfoFile}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var setupInfo = IOUtil.LoadObject<List<SetupInfo>>(setupInfoFile);
|
||||||
|
if (setupInfo?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var info in setupInfo)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(info?.Detail))
|
||||||
|
{
|
||||||
|
var groupName = info.Group;
|
||||||
|
if (string.IsNullOrEmpty(groupName))
|
||||||
|
{
|
||||||
|
groupName = "Machine Setup Info";
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Output($"##[group]{groupName}");
|
||||||
|
var multiLines = info.Detail.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
||||||
|
foreach (var line in multiLines)
|
||||||
|
{
|
||||||
|
context.Output(line);
|
||||||
|
}
|
||||||
|
context.Output("##[endgroup]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Output($"Fail to load and print machine setup info: {ex.Message}");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var repoFullName = context.GetGitHubContext("repository");
|
var repoFullName = context.GetGitHubContext("repository");
|
||||||
ArgUtil.NotNull(repoFullName, nameof(repoFullName));
|
ArgUtil.NotNull(repoFullName, nameof(repoFullName));
|
||||||
context.Debug($"Primary repository: {repoFullName}");
|
context.Debug($"Primary repository: {repoFullName}");
|
||||||
@@ -78,12 +129,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate the job-level environment variables
|
// Evaluate the job-level environment variables
|
||||||
context.Debug("Evaluating job-level environment variables");
|
context.Debug("Evaluating job-level environment variables");
|
||||||
var templateTrace = context.ToTemplateTraceWriter();
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema);
|
|
||||||
foreach (var token in message.EnvironmentVariables)
|
foreach (var token in message.EnvironmentVariables)
|
||||||
{
|
{
|
||||||
var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
|
var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, jobContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
foreach (var pair in environmentVariables)
|
foreach (var pair in environmentVariables)
|
||||||
{
|
{
|
||||||
context.EnvironmentVariables[pair.Key] = pair.Value ?? string.Empty;
|
context.EnvironmentVariables[pair.Key] = pair.Value ?? string.Empty;
|
||||||
@@ -93,7 +142,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate the job container
|
// Evaluate the job container
|
||||||
context.Debug("Evaluating job container");
|
context.Debug("Evaluating job container");
|
||||||
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues);
|
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
if (container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
jobContext.Container = new Container.ContainerInfo(HostContext, container);
|
jobContext.Container = new Container.ContainerInfo(HostContext, container);
|
||||||
@@ -101,7 +150,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate the job service containers
|
// Evaluate the job service containers
|
||||||
context.Debug("Evaluating job service containers");
|
context.Debug("Evaluating job service containers");
|
||||||
var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues);
|
var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
if (serviceContainers?.Count > 0)
|
if (serviceContainers?.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var pair in serviceContainers)
|
foreach (var pair in serviceContainers)
|
||||||
@@ -112,12 +161,32 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluate the job defaults
|
||||||
|
context.Debug("Evaluating job defaults");
|
||||||
|
foreach (var token in message.Defaults)
|
||||||
|
{
|
||||||
|
var defaults = token.AssertMapping("defaults");
|
||||||
|
if (defaults.Any(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
context.JobDefaults["run"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase));
|
||||||
|
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
|
foreach (var pair in jobDefaults)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(pair.Value))
|
||||||
|
{
|
||||||
|
context.JobDefaults["run"][pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build up 2 lists of steps, pre-job, job
|
// Build up 2 lists of steps, pre-job, job
|
||||||
// Download actions not already in the cache
|
// Download actions not already in the cache
|
||||||
Trace.Info("Downloading actions");
|
Trace.Info("Downloading actions");
|
||||||
var actionManager = HostContext.GetService<IActionManager>();
|
var actionManager = HostContext.GetService<IActionManager>();
|
||||||
var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps);
|
var prepareSteps = await actionManager.PrepareActionsAsync(context, message.Steps);
|
||||||
preJobSteps.AddRange(prepareResult.ContainerSetupSteps);
|
preJobSteps.AddRange(prepareSteps);
|
||||||
|
|
||||||
// Add start-container steps, record and stop-container steps
|
// Add start-container steps, record and stop-container steps
|
||||||
if (jobContext.Container != null || jobContext.ServiceContainers.Count > 0)
|
if (jobContext.Container != null || jobContext.ServiceContainers.Count > 0)
|
||||||
@@ -158,22 +227,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
actionRunner.TryEvaluateDisplayName(contextData, context);
|
actionRunner.TryEvaluateDisplayName(contextData, context);
|
||||||
jobSteps.Add(actionRunner);
|
jobSteps.Add(actionRunner);
|
||||||
|
|
||||||
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
|
|
||||||
{
|
|
||||||
Trace.Info($"Adding pre-{action.DisplayName}.");
|
|
||||||
preStep.TryEvaluateDisplayName(contextData, context);
|
|
||||||
preStep.DisplayName = $"Pre {preStep.DisplayName}";
|
|
||||||
preJobSteps.Add(preStep);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
|
|
||||||
foreach (var preStep in prepareResult.PreStepTracker)
|
|
||||||
{
|
|
||||||
intraActionStates[preStep.Key] = new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create execution context for pre-job steps
|
// Create execution context for pre-job steps
|
||||||
foreach (var step in preJobSteps)
|
foreach (var step in preJobSteps)
|
||||||
@@ -185,12 +240,6 @@ namespace GitHub.Runner.Worker
|
|||||||
Guid stepId = Guid.NewGuid();
|
Guid stepId = Guid.NewGuid();
|
||||||
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
||||||
}
|
}
|
||||||
else if (step is IActionRunner actionStep)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(actionStep, step.DisplayName);
|
|
||||||
Guid stepId = Guid.NewGuid();
|
|
||||||
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, null, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionStates[actionStep.Action.Id]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create execution context for job steps
|
// Create execution context for job steps
|
||||||
@@ -199,16 +248,9 @@ namespace GitHub.Runner.Worker
|
|||||||
if (step is IActionRunner actionStep)
|
if (step is IActionRunner actionStep)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(actionStep, step.DisplayName);
|
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||||
if (intraActionStates.ContainsKey(actionStep.Action.Id))
|
|
||||||
{
|
|
||||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionStates[actionStep.Action.Id]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName);
|
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
List<IStep> steps = new List<IStep>();
|
List<IStep> steps = new List<IStep>();
|
||||||
steps.AddRange(preJobSteps);
|
steps.AddRange(preJobSteps);
|
||||||
@@ -271,6 +313,58 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Start();
|
context.Start();
|
||||||
context.Debug("Starting: Complete job");
|
context.Debug("Starting: Complete job");
|
||||||
|
|
||||||
|
// Evaluate job outputs
|
||||||
|
if (message.JobOutputs != null && message.JobOutputs.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.Output($"Evaluate and set job outputs");
|
||||||
|
|
||||||
|
// Populate env context for each step
|
||||||
|
Trace.Info("Initialize Env context for evaluating job outputs");
|
||||||
|
#if OS_WINDOWS
|
||||||
|
var envContext = new DictionaryContextData();
|
||||||
|
#else
|
||||||
|
var envContext = new CaseSensitiveDictionaryContextData();
|
||||||
|
#endif
|
||||||
|
context.ExpressionValues["env"] = envContext;
|
||||||
|
foreach (var pair in context.EnvironmentVariables)
|
||||||
|
{
|
||||||
|
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info("Initialize steps context for evaluating job outputs");
|
||||||
|
context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName);
|
||||||
|
|
||||||
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
|
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues, context.ExpressionFunctions);
|
||||||
|
foreach (var output in outputs)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(output.Value))
|
||||||
|
{
|
||||||
|
context.Debug($"Skip output '{output.Key}' since it's empty");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(output.Value, HostContext.SecretMasker.MaskSecrets(output.Value)))
|
||||||
|
{
|
||||||
|
context.Warning($"Skip output '{output.Key}' since it may contain secret.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Output($"Set output '{output.Key}'");
|
||||||
|
jobContext.JobOutputs[output.Key] = output.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Result = TaskResult.Failed;
|
||||||
|
context.Error($"Fail to evaluate job outputs");
|
||||||
|
context.Error(ex);
|
||||||
|
jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, TaskResult.Failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (context.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false)
|
if (context.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false)
|
||||||
{
|
{
|
||||||
Trace.Info("Support log upload starting.");
|
Trace.Info("Support log upload starting.");
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info("Raising job completed event.");
|
Trace.Info("Raising job completed event.");
|
||||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result);
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs);
|
||||||
|
|
||||||
var completeJobRetryLimit = 5;
|
var completeJobRetryLimit = 5;
|
||||||
var exceptions = new List<Exception>();
|
var exceptions = new List<Exception>();
|
||||||
|
|||||||
@@ -56,13 +56,22 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetResult(
|
public void SetConclusion(
|
||||||
string scopeName,
|
string scopeName,
|
||||||
string stepName,
|
string stepName,
|
||||||
string result)
|
string conclusion)
|
||||||
{
|
{
|
||||||
var step = GetStep(scopeName, stepName);
|
var step = GetStep(scopeName, stepName);
|
||||||
step["result"] = new StringContextData(result);
|
step["conclusion"] = new StringContextData(conclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOutcome(
|
||||||
|
string scopeName,
|
||||||
|
string stepName,
|
||||||
|
string outcome)
|
||||||
|
{
|
||||||
|
var step = GetStep(scopeName, stepName);
|
||||||
|
step["outcome"] = new StringContextData(outcome);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DictionaryContextData GetStep(string scopeName, string stepName)
|
private DictionaryContextData GetStep(string scopeName, string stepName)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
@@ -10,8 +8,13 @@ using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -63,11 +66,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
var step = jobContext.JobSteps.Dequeue();
|
var step = jobContext.JobSteps.Dequeue();
|
||||||
IStep nextStep = null;
|
var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null;
|
||||||
if (jobContext.JobSteps.Count > 0)
|
|
||||||
{
|
|
||||||
nextStep = jobContext.JobSteps.Peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
|
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
|
||||||
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
|
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
|
||||||
@@ -75,7 +74,13 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Start
|
// Start
|
||||||
step.ExecutionContext.Start();
|
step.ExecutionContext.Start();
|
||||||
await step.ExecutionContext.UpdateGitHubTokenInContext();
|
|
||||||
|
// Expression functions
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
|
||||||
|
|
||||||
// Initialize scope
|
// Initialize scope
|
||||||
if (InitializeScope(step, scopeInputs))
|
if (InitializeScope(step, scopeInputs))
|
||||||
@@ -99,17 +104,14 @@ namespace GitHub.Runner.Worker
|
|||||||
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
|
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
|
||||||
|
|
||||||
// Evaluate and merge action's env block to env context
|
// Evaluate and merge action's env block to env context
|
||||||
var templateTrace = step.ExecutionContext.ToTemplateTraceWriter();
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema);
|
|
||||||
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
|
|
||||||
foreach (var env in actionEnvironment)
|
foreach (var env in actionEnvironment)
|
||||||
{
|
{
|
||||||
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var expressionManager = HostContext.GetService<IExpressionManager>();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
||||||
@@ -123,28 +125,29 @@ namespace GitHub.Runner.Worker
|
|||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
|
||||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||||
ConditionResult conditionReTestResult;
|
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||||
|
var conditionReTestResult = false;
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
||||||
conditionReTestResult = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
conditionReTestResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition, hostTracingOnly: true);
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Cancel the step since we get exception while re-evaluate step condition.
|
// Cancel the step since we get exception while re-evaluate step condition.
|
||||||
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
||||||
step.ExecutionContext.Error(ex);
|
step.ExecutionContext.Error(ex);
|
||||||
conditionReTestResult = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!conditionReTestResult.Value)
|
if (!conditionReTestResult)
|
||||||
{
|
{
|
||||||
// Cancel the step.
|
// Cancel the step.
|
||||||
Trace.Info("Cancel current running step.");
|
Trace.Info("Cancel current running step.");
|
||||||
@@ -164,34 +167,35 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate condition.
|
// Evaluate condition.
|
||||||
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
||||||
Exception conditionEvaluateError = null;
|
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
||||||
ConditionResult conditionResult;
|
var conditionResult = false;
|
||||||
|
var conditionEvaluateError = default(Exception);
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
||||||
conditionResult = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
conditionResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition);
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Info("Caught exception from expression.");
|
Trace.Info("Caught exception from expression.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
conditionResult = false;
|
|
||||||
conditionEvaluateError = ex;
|
conditionEvaluateError = ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no evaluate error but condition is false
|
// no evaluate error but condition is false
|
||||||
if (!conditionResult.Value && conditionEvaluateError == null)
|
if (!conditionResult && conditionEvaluateError == null)
|
||||||
{
|
{
|
||||||
// Condition == false
|
// Condition == false
|
||||||
Trace.Info("Skipping step due to condition evaluation.");
|
Trace.Info("Skipping step due to condition evaluation.");
|
||||||
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionResult.Trace);
|
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
||||||
}
|
}
|
||||||
else if (conditionEvaluateError != null)
|
else if (conditionEvaluateError != null)
|
||||||
{
|
{
|
||||||
@@ -248,10 +252,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Set the timeout
|
// Set the timeout
|
||||||
var timeoutMinutes = 0;
|
var timeoutMinutes = 0;
|
||||||
var templateEvaluator = CreateTemplateEvaluator(step.ExecutionContext);
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues);
|
timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -342,7 +346,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var continueOnError = false;
|
var continueOnError = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues);
|
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -354,6 +358,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (continueOnError)
|
if (continueOnError)
|
||||||
{
|
{
|
||||||
|
step.ExecutionContext.Outcome = step.ExecutionContext.Result;
|
||||||
step.ExecutionContext.Result = TaskResult.Succeeded;
|
step.ExecutionContext.Result = TaskResult.Succeeded;
|
||||||
Trace.Info($"Updated step result (continue on error)");
|
Trace.Info($"Updated step result (continue on error)");
|
||||||
}
|
}
|
||||||
@@ -390,11 +395,11 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
||||||
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
||||||
var templateEvaluator = CreateTemplateEvaluator(executionContext);
|
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
var inputs = default(DictionaryContextData);
|
var inputs = default(DictionaryContextData);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues);
|
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -446,11 +451,11 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Debug($"Finalizing scope '{scope.Name}'");
|
executionContext.Debug($"Finalizing scope '{scope.Name}'");
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.Name);
|
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.Name);
|
||||||
executionContext.ExpressionValues["inputs"] = null;
|
executionContext.ExpressionValues["inputs"] = null;
|
||||||
var templateEvaluator = CreateTemplateEvaluator(executionContext);
|
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
var outputs = default(DictionaryContextData);
|
var outputs = default(DictionaryContextData);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues);
|
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -479,11 +484,42 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Complete(result, resultCode: resultCode);
|
executionContext.Complete(result, resultCode: resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PipelineTemplateEvaluator CreateTemplateEvaluator(IExecutionContext executionContext)
|
private sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter
|
||||||
{
|
{
|
||||||
var templateTrace = executionContext.ToTemplateTraceWriter();
|
private readonly IExecutionContext _executionContext;
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
private readonly Tracing _trace;
|
||||||
return new PipelineTemplateEvaluator(templateTrace, schema);
|
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
public string Trace => _traceBuilder.ToString();
|
||||||
|
|
||||||
|
public ConditionTraceWriter(Tracing trace, IExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(trace, nameof(trace));
|
||||||
|
_trace = trace;
|
||||||
|
_executionContext = executionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Error(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Info(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
_traceBuilder.AppendLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Verbose(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,6 @@
|
|||||||
"entrypoint": "non-empty-string",
|
"entrypoint": "non-empty-string",
|
||||||
"args": "container-runs-args",
|
"args": "container-runs-args",
|
||||||
"env": "container-runs-env",
|
"env": "container-runs-env",
|
||||||
"pre-entrypoint": "non-empty-string",
|
|
||||||
"pre-if": "non-empty-string",
|
|
||||||
"post-entrypoint": "non-empty-string",
|
"post-entrypoint": "non-empty-string",
|
||||||
"post-if": "non-empty-string"
|
"post-if": "non-empty-string"
|
||||||
}
|
}
|
||||||
@@ -69,8 +67,6 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"using": "non-empty-string",
|
"using": "non-empty-string",
|
||||||
"main": "non-empty-string",
|
"main": "non-empty-string",
|
||||||
"pre": "non-empty-string",
|
|
||||||
"pre-if": "non-empty-string",
|
|
||||||
"post": "non-empty-string",
|
"post": "non-empty-string",
|
||||||
"post-if": "non-empty-string"
|
"post-if": "non-empty-string"
|
||||||
}
|
}
|
||||||
@@ -94,10 +90,9 @@
|
|||||||
"github",
|
"github",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"steps",
|
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using GitHub.DistributedTask.Expressions2.Sdk.Functions;
|
|||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions2
|
namespace GitHub.DistributedTask.Expressions2
|
||||||
{
|
{
|
||||||
public static class ExpressionConstants
|
internal static class ExpressionConstants
|
||||||
{
|
{
|
||||||
static ExpressionConstants()
|
static ExpressionConstants()
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
AddFunction<Join>("join", 1, 2);
|
AddFunction<Join>("join", 1, 2);
|
||||||
AddFunction<StartsWith>("startsWith", 2, 2);
|
AddFunction<StartsWith>("startsWith", 2, 2);
|
||||||
AddFunction<ToJson>("toJson", 1, 1);
|
AddFunction<ToJson>("toJson", 1, 1);
|
||||||
AddFunction<HashFiles>("hashFiles", 1, 1);
|
AddFunction<FromJson>("fromJson", 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
||||||
@@ -24,12 +24,6 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
|
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
|
||||||
where T : Function, new()
|
|
||||||
{
|
|
||||||
WellKnownFunctions[name] = new FunctionInfo<T>(name, minParameters, maxParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static readonly String False = "false";
|
internal static readonly String False = "false";
|
||||||
internal static readonly String Infinity = "Infinity";
|
internal static readonly String Infinity = "Infinity";
|
||||||
internal static readonly Int32 MaxDepth = 50;
|
internal static readonly Int32 MaxDepth = 50;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
|
||||||
|
{
|
||||||
|
internal sealed class FromJson : Function
|
||||||
|
{
|
||||||
|
protected sealed override Object EvaluateCore(
|
||||||
|
EvaluationContext context,
|
||||||
|
out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var json = Parameters[0].Evaluate(context).ConvertToString();
|
||||||
|
using (var stringReader = new StringReader(json))
|
||||||
|
using (var jsonReader = new JsonTextReader(stringReader) { DateParseHandling = DateParseHandling.None, FloatParseHandling = FloatParseHandling.Double })
|
||||||
|
{
|
||||||
|
var token = JToken.ReadFrom(jsonReader);
|
||||||
|
return token.ToPipelineContextData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Minimatch;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
|
||||||
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
|
|
||||||
{
|
|
||||||
internal sealed class HashFiles : Function
|
|
||||||
{
|
|
||||||
protected sealed override Object EvaluateCore(
|
|
||||||
EvaluationContext context,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
|
|
||||||
// hashFiles() only works on the runner and only works with files under GITHUB_WORKSPACE
|
|
||||||
// Since GITHUB_WORKSPACE is set by runner, I am using that as the fact of this code runs on server or runner.
|
|
||||||
if (context.State is ObjectTemplating.TemplateContext templateContext &&
|
|
||||||
templateContext.ExpressionValues.TryGetValue(PipelineTemplateConstants.GitHub, out var githubContextData) &&
|
|
||||||
githubContextData is DictionaryContextData githubContext &&
|
|
||||||
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out var workspace) == true &&
|
|
||||||
workspace is StringContextData workspaceData)
|
|
||||||
{
|
|
||||||
string searchRoot = workspaceData.Value;
|
|
||||||
string pattern = Parameters[0].Evaluate(context).ConvertToString();
|
|
||||||
|
|
||||||
// Convert slashes on Windows
|
|
||||||
if (s_isWindows)
|
|
||||||
{
|
|
||||||
pattern = pattern.Replace('\\', '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root the pattern
|
|
||||||
if (!Path.IsPathRooted(pattern))
|
|
||||||
{
|
|
||||||
var patternRoot = s_isWindows ? searchRoot.Replace('\\', '/').TrimEnd('/') : searchRoot.TrimEnd('/');
|
|
||||||
pattern = string.Concat(patternRoot, "/", pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all files
|
|
||||||
context.Trace.Info($"Search root directory: '{searchRoot}'");
|
|
||||||
context.Trace.Info($"Search pattern: '{pattern}'");
|
|
||||||
var files = Directory.GetFiles(searchRoot, "*", SearchOption.AllDirectories)
|
|
||||||
.Select(x => s_isWindows ? x.Replace('\\', '/') : x)
|
|
||||||
.OrderBy(x => x, StringComparer.Ordinal)
|
|
||||||
.ToList();
|
|
||||||
if (files.Count == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Directory '{searchRoot}' is empty");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Info($"Found {files.Count} files");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match
|
|
||||||
var matcher = new Minimatcher(pattern, s_minimatchOptions);
|
|
||||||
files = matcher.Filter(files)
|
|
||||||
.Select(x => s_isWindows ? x.Replace('/', '\\') : x)
|
|
||||||
.ToList();
|
|
||||||
if (files.Count == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Search pattern '{pattern}' doesn't match any file under '{searchRoot}'");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Info($"{files.Count} matches to hash");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash each file
|
|
||||||
List<byte> filesSha256 = new List<byte>();
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
context.Trace.Info($"Hash {file}");
|
|
||||||
using (SHA256 sha256hash = SHA256.Create())
|
|
||||||
{
|
|
||||||
using (var fileStream = File.OpenRead(file))
|
|
||||||
{
|
|
||||||
filesSha256.AddRange(sha256hash.ComputeHash(fileStream));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the hashes
|
|
||||||
using (SHA256 sha256hash = SHA256.Create())
|
|
||||||
{
|
|
||||||
var hashBytes = sha256hash.ComputeHash(filesSha256.ToArray());
|
|
||||||
StringBuilder hashString = new StringBuilder();
|
|
||||||
for (int i = 0; i < hashBytes.Length; i++)
|
|
||||||
{
|
|
||||||
hashString.Append(hashBytes[i].ToString("x2"));
|
|
||||||
}
|
|
||||||
var result = hashString.ToString();
|
|
||||||
context.Trace.Info($"Final hash result: '{result}'");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("'hashfiles' expression function is only supported under runner context.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly bool s_isWindows = Environment.OSVersion.Platform != PlatformID.Unix && Environment.OSVersion.Platform != PlatformID.MacOSX;
|
|
||||||
|
|
||||||
// Only support basic globbing (* ? and []) and globstar (**)
|
|
||||||
private static readonly Options s_minimatchOptions = new Options
|
|
||||||
{
|
|
||||||
Dot = true,
|
|
||||||
NoBrace = true,
|
|
||||||
NoCase = s_isWindows,
|
|
||||||
NoComment = true,
|
|
||||||
NoExt = true,
|
|
||||||
NoNegate = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -779,5 +779,65 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [Preview API]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolId"></param>
|
||||||
|
/// <param name="agentId"></param>
|
||||||
|
/// <param name="userState"></param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
public Task<String> GetAgentAuthUrlAsync(
|
||||||
|
int poolId,
|
||||||
|
int agentId,
|
||||||
|
object userState = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("GET");
|
||||||
|
Guid locationId = new Guid("a82a119c-1e46-44b6-8d75-c82a79cf975b");
|
||||||
|
object routeValues = new { poolId = poolId, agentId = agentId };
|
||||||
|
|
||||||
|
return SendAsync<String>(
|
||||||
|
httpMethod,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(6.0, 1),
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [Preview API]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolId"></param>
|
||||||
|
/// <param name="agentId"></param>
|
||||||
|
/// <param name="error"></param>
|
||||||
|
/// <param name="userState"></param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public virtual async Task ReportAgentAuthUrlMigrationErrorAsync(
|
||||||
|
int poolId,
|
||||||
|
int agentId,
|
||||||
|
string error,
|
||||||
|
object userState = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
|
Guid locationId = new Guid("a82a119c-1e46-44b6-8d75-c82a79cf975b");
|
||||||
|
object routeValues = new { poolId = poolId, agentId = agentId };
|
||||||
|
HttpContent content = new ObjectContent<string>(error, new VssJsonMediaTypeFormatter(true));
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = await SendAsync(
|
||||||
|
httpMethod,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(6.0, 1),
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken,
|
||||||
|
content: content).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,40 +317,5 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// [Preview API]
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
|
||||||
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
|
||||||
/// <param name="planId"></param>
|
|
||||||
/// <param name="jobId"></param>
|
|
||||||
/// <param name="userState"></param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
|
||||||
public virtual Task<GitHubToken> RefreshTokenAsync(
|
|
||||||
Guid scopeIdentifier,
|
|
||||||
string hubName,
|
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("8aa8aff7-751b-496e-be8d-b7818770efb3");
|
|
||||||
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
|
||||||
queryParams.Add("jobId", jobId.ToString());
|
|
||||||
|
|
||||||
return SendAsync<GitHubToken>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(6.0, 1),
|
|
||||||
queryParameters: queryParams,
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
|
||||||
@@ -22,10 +23,27 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
{
|
{
|
||||||
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
|
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
|
||||||
definition.RemoveAt(i);
|
definition.RemoveAt(i);
|
||||||
Context = context
|
var readerContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
.Select(x => x.AssertString($"{TemplateConstants.Context} item").Value)
|
var evaluatorContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
.Distinct()
|
foreach (TemplateToken item in context)
|
||||||
.ToArray();
|
{
|
||||||
|
var itemStr = item.AssertString($"{TemplateConstants.Context} item").Value;
|
||||||
|
readerContext.Add(itemStr);
|
||||||
|
|
||||||
|
// Remove min/max parameter info
|
||||||
|
var paramIndex = itemStr.IndexOf('(');
|
||||||
|
if (paramIndex > 0)
|
||||||
|
{
|
||||||
|
evaluatorContext.Add(String.Concat(itemStr.Substring(0, paramIndex + 1), ")"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
evaluatorContext.Add(itemStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReaderContext = readerContext.ToArray();
|
||||||
|
EvaluatorContext = evaluatorContext.ToArray();
|
||||||
}
|
}
|
||||||
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
|
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -40,7 +58,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
internal abstract DefinitionType DefinitionType { get; }
|
internal abstract DefinitionType DefinitionType { get; }
|
||||||
|
|
||||||
internal String[] Context { get; private set; } = new String[0];
|
/// <summary>
|
||||||
|
/// Used by the template reader to determine allowed expression values and functions.
|
||||||
|
/// Also used by the template reader to validate function min/max parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal String[] ReaderContext { get; private set; } = new String[0];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by the template evaluator to determine allowed expression values and functions.
|
||||||
|
/// The min/max parameter info is omitted.
|
||||||
|
/// </summary>
|
||||||
|
internal String[] EvaluatorContext { get; private set; } = new String[0];
|
||||||
|
|
||||||
internal abstract void Validate(
|
internal abstract void Validate(
|
||||||
TemplateSchema schema,
|
TemplateSchema schema,
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
foreach (var propertiesPair in properties)
|
foreach (var propertiesPair in properties)
|
||||||
{
|
{
|
||||||
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
|
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
|
||||||
var propertyValue = propertiesPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} value");
|
Properties.Add(propertyName.Value, new PropertyValue(propertiesPair.Value));
|
||||||
Properties.Add(propertyName.Value, new PropertyValue(propertyValue.Value));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined");
|
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined on '{name}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise validate loose value type not be defined
|
// Otherwise validate loose value type not be defined
|
||||||
@@ -95,16 +94,21 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lookup each property
|
// Lookup each property
|
||||||
foreach (var property in Properties.Values)
|
foreach (var property in Properties)
|
||||||
{
|
{
|
||||||
schema.GetDefinition(property.Type);
|
if (String.IsNullOrEmpty(property.Value.Type))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Type not specified for the '{property.Key}' property on the '{name}' type");
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.GetDefinition(property.Value.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(Inherits))
|
if (!String.IsNullOrEmpty(Inherits))
|
||||||
{
|
{
|
||||||
var inherited = schema.GetDefinition(Inherits);
|
var inherited = schema.GetDefinition(Inherits);
|
||||||
|
|
||||||
if (inherited.Context.Length > 0)
|
if (inherited.ReaderContext.Length > 0)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
|
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
{
|
{
|
||||||
var nestedDefinition = schema.GetDefinition(nestedType);
|
var nestedDefinition = schema.GetDefinition(nestedType);
|
||||||
|
|
||||||
if (nestedDefinition.Context.Length > 0)
|
if (nestedDefinition.ReaderContext.Length > 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
|
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,40 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||||
{
|
{
|
||||||
internal sealed class PropertyValue
|
internal sealed class PropertyValue
|
||||||
{
|
{
|
||||||
internal PropertyValue()
|
internal PropertyValue(TemplateToken token)
|
||||||
{
|
{
|
||||||
|
if (token is StringToken stringToken)
|
||||||
|
{
|
||||||
|
Type = stringToken.Value;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
internal PropertyValue(String type)
|
|
||||||
{
|
{
|
||||||
Type = type;
|
var mapping = token.AssertMapping($"{TemplateConstants.MappingPropertyValue}");
|
||||||
|
foreach (var mappingPair in mapping)
|
||||||
|
{
|
||||||
|
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.MappingPropertyValue} key");
|
||||||
|
switch (mappingKey.Value)
|
||||||
|
{
|
||||||
|
case TemplateConstants.Type:
|
||||||
|
Type = mappingPair.Value.AssertString($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Type}").Value;
|
||||||
|
break;
|
||||||
|
case TemplateConstants.Required:
|
||||||
|
Required = mappingPair.Value.AssertBoolean($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Required}").Value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mappingKey.AssertUnexpectedValue($"{TemplateConstants.MappingPropertyValue} key");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal String Type { get; set; }
|
internal String Type { get; set; }
|
||||||
|
|
||||||
|
internal Boolean Required { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,8 +312,8 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// template-schema
|
// template-schema
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(TemplateConstants.Definitions));
|
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Definitions)));
|
||||||
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
|
||||||
|
|
||||||
// definitions
|
// definitions
|
||||||
@@ -335,9 +335,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// null-definition
|
// null-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(TemplateConstants.NullDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NullDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
|
||||||
|
|
||||||
// null-definition-properties
|
// null-definition-properties
|
||||||
@@ -346,9 +346,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// boolean-definition
|
// boolean-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(TemplateConstants.BooleanDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(new StringToken(null, null, null, TemplateConstants.BooleanDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
|
||||||
|
|
||||||
// boolean-definition-properties
|
// boolean-definition-properties
|
||||||
@@ -357,9 +357,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// number-definition
|
// number-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(TemplateConstants.NumberDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NumberDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
|
||||||
|
|
||||||
// number-definition-properties
|
// number-definition-properties
|
||||||
@@ -368,55 +368,68 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// string-definition
|
// string-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(TemplateConstants.StringDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(new StringToken(null, null, null, TemplateConstants.StringDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
|
||||||
|
|
||||||
// string-definition-properties
|
// string-definition-properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(TemplateConstants.Boolean));
|
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(new StringToken(null, null, null,TemplateConstants.Boolean)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(TemplateConstants.Boolean));
|
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||||
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
||||||
|
|
||||||
// sequence-definition
|
// sequence-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(TemplateConstants.SequenceDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
|
||||||
|
|
||||||
// sequence-definition-properties
|
// sequence-definition-properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
||||||
|
|
||||||
// mapping-definition
|
// mapping-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(TemplateConstants.MappingDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(new StringToken(null, null, null, TemplateConstants.MappingDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
|
||||||
|
|
||||||
// mapping-definition-properties
|
// mapping-definition-properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(TemplateConstants.Properties));
|
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Properties)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
|
||||||
|
|
||||||
// properties
|
// properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
||||||
mappingDefinition.LooseValueType = TemplateConstants.NonEmptyString;
|
mappingDefinition.LooseValueType = TemplateConstants.PropertyValue;
|
||||||
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
|
||||||
|
|
||||||
|
// property-value
|
||||||
|
oneOfDefinition = new OneOfDefinition();
|
||||||
|
oneOfDefinition.OneOf.Add(TemplateConstants.NonEmptyString);
|
||||||
|
oneOfDefinition.OneOf.Add(TemplateConstants.MappingPropertyValue);
|
||||||
|
schema.Definitions.Add(TemplateConstants.PropertyValue, oneOfDefinition);
|
||||||
|
|
||||||
|
// mapping-property-value
|
||||||
|
mappingDefinition = new MappingDefinition();
|
||||||
|
mappingDefinition.Properties.Add(TemplateConstants.Type, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
|
mappingDefinition.Properties.Add(TemplateConstants.Required, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||||
|
schema.Definitions.Add(TemplateConstants.MappingPropertyValue, mappingDefinition);
|
||||||
|
|
||||||
|
|
||||||
// one-of-definition
|
// one-of-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
|
||||||
|
|
||||||
// non-empty-string
|
// non-empty-string
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
internal const String ItemType = "item-type";
|
internal const String ItemType = "item-type";
|
||||||
internal const String LooseKeyType = "loose-key-type";
|
internal const String LooseKeyType = "loose-key-type";
|
||||||
internal const String LooseValueType = "loose-value-type";
|
internal const String LooseValueType = "loose-value-type";
|
||||||
|
internal const String MaxConstant = "MAX";
|
||||||
internal const String Mapping = "mapping";
|
internal const String Mapping = "mapping";
|
||||||
internal const String MappingDefinition = "mapping-definition";
|
internal const String MappingDefinition = "mapping-definition";
|
||||||
internal const String MappingDefinitionProperties = "mapping-definition-properties";
|
internal const String MappingDefinitionProperties = "mapping-definition-properties";
|
||||||
|
internal const String MappingPropertyValue = "mapping-property-value";
|
||||||
internal const String NonEmptyString = "non-empty-string";
|
internal const String NonEmptyString = "non-empty-string";
|
||||||
internal const String Null = "null";
|
internal const String Null = "null";
|
||||||
internal const String NullDefinition = "null-definition";
|
internal const String NullDefinition = "null-definition";
|
||||||
@@ -35,7 +37,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
internal const String OneOf = "one-of";
|
internal const String OneOf = "one-of";
|
||||||
internal const String OneOfDefinition = "one-of-definition";
|
internal const String OneOfDefinition = "one-of-definition";
|
||||||
internal const String OpenExpression = "${{";
|
internal const String OpenExpression = "${{";
|
||||||
|
internal const String PropertyValue = "property-value";
|
||||||
internal const String Properties = "properties";
|
internal const String Properties = "properties";
|
||||||
|
internal const String Required = "required";
|
||||||
internal const String RequireNonEmpty = "require-non-empty";
|
internal const String RequireNonEmpty = "require-non-empty";
|
||||||
internal const String Scalar = "scalar";
|
internal const String Scalar = "scalar";
|
||||||
internal const String ScalarDefinition = "scalar-definition";
|
internal const String ScalarDefinition = "scalar-definition";
|
||||||
@@ -43,6 +47,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
internal const String Sequence = "sequence";
|
internal const String Sequence = "sequence";
|
||||||
internal const String SequenceDefinition = "sequence-definition";
|
internal const String SequenceDefinition = "sequence-definition";
|
||||||
internal const String SequenceDefinitionProperties = "sequence-definition-properties";
|
internal const String SequenceDefinitionProperties = "sequence-definition-properties";
|
||||||
|
internal const String Type = "type";
|
||||||
internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string";
|
internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string";
|
||||||
internal const String String = "string";
|
internal const String String = "string";
|
||||||
internal const String StringDefinition = "string-definition";
|
internal const String StringDefinition = "string-definition";
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
id = FileIds.Count + 1;
|
id = FileIds.Count + 1;
|
||||||
FileIds.Add(file, id);
|
FileIds.Add(file, id);
|
||||||
FileNames.Add(file);
|
FileNames.Add(file);
|
||||||
|
Memory.AddBytes(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
@@ -191,7 +192,12 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
|
|
||||||
internal String GetFileName(Int32 fileId)
|
internal String GetFileName(Int32 fileId)
|
||||||
{
|
{
|
||||||
return FileNames[fileId - 1];
|
return FileNames.Count >= fileId ? FileNames[fileId - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IReadOnlyList<String> GetFileTable()
|
||||||
|
{
|
||||||
|
return FileNames.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String GetErrorPrefix(
|
private String GetErrorPrefix(
|
||||||
@@ -199,9 +205,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
Int32? line,
|
Int32? line,
|
||||||
Int32? column)
|
Int32? column)
|
||||||
{
|
{
|
||||||
if (fileId != null)
|
var fileName = fileId.HasValue ? GetFileName(fileId.Value) : null;
|
||||||
|
if (!String.IsNullOrEmpty(fileName))
|
||||||
{
|
{
|
||||||
var fileName = GetFileName(fileId.Value);
|
|
||||||
if (line != null && column != null)
|
if (line != null && column != null)
|
||||||
{
|
{
|
||||||
return $"{fileName} {TemplateStrings.LineColumn(line, column)}:";
|
return $"{fileName} {TemplateStrings.LineColumn(line, column)}:";
|
||||||
|
|||||||
@@ -47,7 +47,16 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
var evaluator = new TemplateEvaluator(context, template, removeBytes);
|
var evaluator = new TemplateEvaluator(context, template, removeBytes);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var availableContext = new HashSet<String>(context.ExpressionValues.Keys);
|
var availableContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var key in context.ExpressionValues.Keys)
|
||||||
|
{
|
||||||
|
availableContext.Add(key);
|
||||||
|
}
|
||||||
|
foreach (var function in context.ExpressionFunctions)
|
||||||
|
{
|
||||||
|
availableContext.Add($"{function.Name}()");
|
||||||
|
}
|
||||||
|
|
||||||
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
|
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
|
||||||
result = evaluator.Evaluate(definitionInfo);
|
result = evaluator.Evaluate(definitionInfo);
|
||||||
|
|
||||||
@@ -182,12 +191,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var hasExpressionKey = false;
|
||||||
|
|
||||||
while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
|
while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
|
||||||
{
|
{
|
||||||
// Expression
|
// Expression
|
||||||
if (nextKeyScalar is ExpressionToken)
|
if (nextKeyScalar is ExpressionToken)
|
||||||
{
|
{
|
||||||
|
hasExpressionKey = true;
|
||||||
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
|
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
|
||||||
mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
|
mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
|
||||||
continue;
|
continue;
|
||||||
@@ -268,6 +279,19 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
||||||
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
||||||
}
|
}
|
||||||
|
else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
|
||||||
|
{
|
||||||
|
foreach (var property in mappingDefinitions[0].Properties)
|
||||||
|
{
|
||||||
|
if (property.Value.Required)
|
||||||
|
{
|
||||||
|
if (!keys.Contains(property.Key))
|
||||||
|
{
|
||||||
|
m_context.Error(mapping, $"Required property is missing: {property.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_unraveler.ReadMappingEnd();
|
m_unraveler.ReadMappingEnd();
|
||||||
}
|
}
|
||||||
@@ -378,14 +402,13 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Determine whether to expand
|
||||||
if (Definition.Context.Length > 0)
|
m_allowedContext = Definition.EvaluatorContext;
|
||||||
|
if (Definition.EvaluatorContext.Length > 0)
|
||||||
{
|
{
|
||||||
m_allowedContext = Definition.Context;
|
|
||||||
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_allowedContext = new String[0];
|
|
||||||
Expand = false;
|
Expand = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,9 +424,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Determine whether to expand
|
||||||
if (Definition.Context.Length > 0)
|
if (Definition.EvaluatorContext.Length > 0)
|
||||||
{
|
{
|
||||||
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.Context)).ToArray();
|
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.EvaluatorContext), StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TemplateValidationException(
|
||||||
|
String message,
|
||||||
|
IEnumerable<TemplateValidationError> errors)
|
||||||
|
: this(message)
|
||||||
|
{
|
||||||
|
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
||||||
|
}
|
||||||
|
|
||||||
public TemplateValidationException(String message)
|
public TemplateValidationException(String message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -178,14 +178,15 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var hasExpressionKey = false;
|
||||||
|
|
||||||
while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral))
|
while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral))
|
||||||
{
|
{
|
||||||
var nextKeyScalar = ParseScalar(rawLiteral, definition.AllowedContext);
|
var nextKeyScalar = ParseScalar(rawLiteral, definition.AllowedContext);
|
||||||
|
|
||||||
// Expression
|
// Expression
|
||||||
if (nextKeyScalar is ExpressionToken)
|
if (nextKeyScalar is ExpressionToken)
|
||||||
{
|
{
|
||||||
|
hasExpressionKey = true;
|
||||||
// Legal
|
// Legal
|
||||||
if (definition.AllowedContext.Length > 0)
|
if (definition.AllowedContext.Length > 0)
|
||||||
{
|
{
|
||||||
@@ -280,7 +281,19 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
||||||
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
||||||
}
|
}
|
||||||
|
else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
|
||||||
|
{
|
||||||
|
foreach (var property in mappingDefinitions[0].Properties)
|
||||||
|
{
|
||||||
|
if (property.Value.Required)
|
||||||
|
{
|
||||||
|
if (!keys.Contains(property.Key))
|
||||||
|
{
|
||||||
|
m_context.Error(mapping, $"Required property is missing: {property.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ExpectMappingEnd();
|
ExpectMappingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,15 +780,8 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
// Lookup the definition
|
// Lookup the definition
|
||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Record allowed context
|
||||||
if (Definition.Context.Length > 0)
|
AllowedContext = Definition.ReaderContext;
|
||||||
{
|
|
||||||
AllowedContext = Definition.Context;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AllowedContext = new String[0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefinitionInfo(
|
public DefinitionInfo(
|
||||||
@@ -787,10 +793,10 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
// Lookup the definition
|
// Lookup the definition
|
||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Record allowed context
|
||||||
if (Definition.Context.Length > 0)
|
if (Definition.ReaderContext.Length > 0)
|
||||||
{
|
{
|
||||||
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.Context)).ToArray();
|
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.ReaderContext), StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.ObjectTemplating
|
namespace GitHub.DistributedTask.ObjectTemplating
|
||||||
@@ -41,7 +42,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < 50; i++)
|
for (int i = 0; i < 50; i++)
|
||||||
{
|
{
|
||||||
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.Message;
|
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.ToString();
|
||||||
Add(new TemplateValidationError(message));
|
Add(new TemplateValidationError(message));
|
||||||
if (ex.InnerException == null)
|
if (ex.InnerException == null)
|
||||||
{
|
{
|
||||||
@@ -88,6 +89,23 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws <c ref="TemplateValidationException" /> if any errors.
|
||||||
|
/// <param name="prefix">The error message prefix</param>
|
||||||
|
/// </summary>
|
||||||
|
public void Check(String prefix)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(prefix))
|
||||||
|
{
|
||||||
|
this.Check();
|
||||||
|
}
|
||||||
|
else if (m_errors.Count > 0)
|
||||||
|
{
|
||||||
|
var message = $"{prefix.Trim()} {String.Join(",", m_errors.Select(e => e.Message))}";
|
||||||
|
throw new TemplateValidationException(message, m_errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
m_errors.Clear();
|
m_errors.Clear();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Globalization;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
using GitHub.Services.WebApi.Internal;
|
using GitHub.Services.WebApi.Internal;
|
||||||
@@ -35,11 +37,29 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
String[] allowedContext,
|
String[] allowedContext,
|
||||||
out Exception ex)
|
out Exception ex)
|
||||||
{
|
{
|
||||||
// Create dummy allowed contexts
|
// Create dummy named values and functions
|
||||||
INamedValueInfo[] namedValues = null;
|
var namedValues = new List<INamedValueInfo>();
|
||||||
|
var functions = new List<IFunctionInfo>();
|
||||||
if (allowedContext?.Length > 0)
|
if (allowedContext?.Length > 0)
|
||||||
{
|
{
|
||||||
namedValues = allowedContext.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
foreach (var contextItem in allowedContext)
|
||||||
|
{
|
||||||
|
var match = s_function.Match(contextItem);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var functionName = match.Groups[1].Value;
|
||||||
|
var minParameters = Int32.Parse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture);
|
||||||
|
var maxParametersRaw = match.Groups[3].Value;
|
||||||
|
var maxParameters = String.Equals(maxParametersRaw, TemplateConstants.MaxConstant, StringComparison.Ordinal)
|
||||||
|
? Int32.MaxValue
|
||||||
|
: Int32.Parse(maxParametersRaw, NumberStyles.None, CultureInfo.InvariantCulture);
|
||||||
|
functions.Add(new FunctionInfo<DummyFunction>(functionName, minParameters, maxParameters));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
namedValues.Add(new NamedValueInfo<ContextValueNode>(contextItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
@@ -47,7 +67,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
ExpressionNode root = null;
|
ExpressionNode root = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode;
|
root = new ExpressionParser().CreateTree(expression, null, namedValues, functions) as ExpressionNode;
|
||||||
|
|
||||||
result = true;
|
result = true;
|
||||||
ex = null;
|
ex = null;
|
||||||
@@ -60,5 +80,18 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class DummyFunction : Function
|
||||||
|
{
|
||||||
|
protected override Object EvaluateCore(
|
||||||
|
EvaluationContext context,
|
||||||
|
out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex s_function = new Regex(@"^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$", RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
Column = column;
|
Column = column;
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[DataMember(Name = "file", EmitDefaultValue = false)]
|
||||||
internal Int32? FileId { get; set; }
|
internal Int32? FileId { get; private set; }
|
||||||
|
|
||||||
[DataMember(Name = "line", EmitDefaultValue = false)]
|
[DataMember(Name = "line", EmitDefaultValue = false)]
|
||||||
internal Int32? Line { get; }
|
internal Int32? Line { get; private set; }
|
||||||
|
|
||||||
[DataMember(Name = "col", EmitDefaultValue = false)]
|
[DataMember(Name = "col", EmitDefaultValue = false)]
|
||||||
internal Int32? Column { get; }
|
internal Int32? Column { get; private set; }
|
||||||
|
|
||||||
[DataMember(Name = "type", EmitDefaultValue = false)]
|
[DataMember(Name = "type", EmitDefaultValue = false)]
|
||||||
internal Int32 Type { get; }
|
internal Int32 Type { get; }
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||||
{
|
{
|
||||||
@@ -106,6 +109,43 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
|
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Traverses the token and checks whether all required expression values
|
||||||
|
/// and functions are provided.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CheckHasRequiredContext(
|
||||||
|
this TemplateToken token,
|
||||||
|
IReadOnlyObject expressionValues,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
|
{
|
||||||
|
var expressionTokens = token.Traverse()
|
||||||
|
.OfType<BasicExpressionToken>()
|
||||||
|
.ToArray();
|
||||||
|
var parser = new ExpressionParser();
|
||||||
|
foreach (var expressionToken in expressionTokens)
|
||||||
|
{
|
||||||
|
var tree = parser.ValidateSyntax(expressionToken.Expression, null);
|
||||||
|
foreach (var node in tree.Traverse())
|
||||||
|
{
|
||||||
|
if (node is NamedValue namedValue)
|
||||||
|
{
|
||||||
|
if (expressionValues?.Keys.Any(x => string.Equals(x, namedValue.Name, StringComparison.OrdinalIgnoreCase)) != true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node is Function function &&
|
||||||
|
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name) &&
|
||||||
|
expressionFunctions?.Any(x => string.Equals(x.Name, function.Name, StringComparison.OrdinalIgnoreCase)) != true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all tokens (depth first)
|
/// Returns all tokens (depth first)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -115,13 +115,12 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
Object value,
|
Object value,
|
||||||
JsonSerializer serializer)
|
JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
base.WriteJson(writer, value, serializer);
|
|
||||||
if (value is TemplateToken token)
|
if (value is TemplateToken token)
|
||||||
{
|
{
|
||||||
switch (token.Type)
|
switch (token.Type)
|
||||||
{
|
{
|
||||||
case TokenType.Null:
|
case TokenType.Null:
|
||||||
if (token.Line == null && token.Column == null)
|
if (token.FileId == null && token.Line == null && token.Column == null)
|
||||||
{
|
{
|
||||||
writer.WriteNull();
|
writer.WriteNull();
|
||||||
}
|
}
|
||||||
@@ -130,12 +129,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -146,7 +150,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
|
|
||||||
case TokenType.Boolean:
|
case TokenType.Boolean:
|
||||||
var booleanToken = token as BooleanToken;
|
var booleanToken = token as BooleanToken;
|
||||||
if (token.Line == null && token.Column == null)
|
if (token.FileId == null && token.Line == null && token.Column == null)
|
||||||
{
|
{
|
||||||
writer.WriteValue(booleanToken.Value);
|
writer.WriteValue(booleanToken.Value);
|
||||||
}
|
}
|
||||||
@@ -155,12 +159,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -173,7 +182,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
|
|
||||||
case TokenType.Number:
|
case TokenType.Number:
|
||||||
var numberToken = token as NumberToken;
|
var numberToken = token as NumberToken;
|
||||||
if (token.Line == null && token.Column == null)
|
if (token.FileId == null && token.Line == null && token.Column == null)
|
||||||
{
|
{
|
||||||
writer.WriteValue(numberToken.Value);
|
writer.WriteValue(numberToken.Value);
|
||||||
}
|
}
|
||||||
@@ -182,12 +191,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -200,7 +214,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
|
|
||||||
case TokenType.String:
|
case TokenType.String:
|
||||||
var stringToken = token as StringToken;
|
var stringToken = token as StringToken;
|
||||||
if (token.Line == null && token.Column == null)
|
if (token.FileId == null && token.Line == null && token.Column == null)
|
||||||
{
|
{
|
||||||
writer.WriteValue(stringToken.Value);
|
writer.WriteValue(stringToken.Value);
|
||||||
}
|
}
|
||||||
@@ -209,12 +223,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -230,12 +249,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -253,12 +277,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -273,12 +302,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
@@ -301,12 +335,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WritePropertyName("type");
|
writer.WritePropertyName("type");
|
||||||
writer.WriteValue(token.Type);
|
writer.WriteValue(token.Type);
|
||||||
|
if (token.FileId != null)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName("file");
|
||||||
|
writer.WriteValue(token.FileId);
|
||||||
|
}
|
||||||
if (token.Line != null)
|
if (token.Line != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("line");
|
writer.WritePropertyName("line");
|
||||||
writer.WriteValue(token.Line);
|
writer.WriteValue(token.Line);
|
||||||
}
|
}
|
||||||
if (token.Line != null)
|
if (token.Column != null)
|
||||||
{
|
{
|
||||||
writer.WritePropertyName("col");
|
writer.WritePropertyName("col");
|
||||||
writer.WriteValue(token.Column);
|
writer.WriteValue(token.Column);
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
WorkspaceOptions workspaceOptions,
|
WorkspaceOptions workspaceOptions,
|
||||||
IEnumerable<JobStep> steps,
|
IEnumerable<JobStep> steps,
|
||||||
IEnumerable<ContextScope> scopes)
|
IEnumerable<ContextScope> scopes,
|
||||||
|
IList<String> fileTable,
|
||||||
|
TemplateToken jobOutputs,
|
||||||
|
IList<TemplateToken> defaults)
|
||||||
{
|
{
|
||||||
this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest;
|
this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest;
|
||||||
this.Plan = plan;
|
this.Plan = plan;
|
||||||
@@ -51,6 +54,7 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
this.Timeline = timeline;
|
this.Timeline = timeline;
|
||||||
this.Resources = jobResources;
|
this.Resources = jobResources;
|
||||||
this.Workspace = workspaceOptions;
|
this.Workspace = workspaceOptions;
|
||||||
|
this.JobOutputs = jobOutputs;
|
||||||
|
|
||||||
m_variables = new Dictionary<String, VariableValue>(variables, StringComparer.OrdinalIgnoreCase);
|
m_variables = new Dictionary<String, VariableValue>(variables, StringComparer.OrdinalIgnoreCase);
|
||||||
m_maskHints = new List<MaskHint>(maskHints);
|
m_maskHints = new List<MaskHint>(maskHints);
|
||||||
@@ -66,6 +70,11 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
m_environmentVariables = new List<TemplateToken>(environmentVariables);
|
m_environmentVariables = new List<TemplateToken>(environmentVariables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaults?.Count > 0)
|
||||||
|
{
|
||||||
|
m_defaults = new List<TemplateToken>(defaults);
|
||||||
|
}
|
||||||
|
|
||||||
this.ContextData = new Dictionary<String, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
this.ContextData = new Dictionary<String, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
||||||
if (contextData?.Count > 0)
|
if (contextData?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -74,6 +83,11 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
this.ContextData[pair.Key] = pair.Value;
|
this.ContextData[pair.Key] = pair.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileTable?.Count > 0)
|
||||||
|
{
|
||||||
|
m_fileTable = new List<String>(fileTable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
@@ -132,6 +146,13 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public TemplateToken JobOutputs
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public Int64 RequestId
|
public Int64 RequestId
|
||||||
{
|
{
|
||||||
@@ -198,6 +219,21 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the hierarchy of defaults to overlay, last wins.
|
||||||
|
/// </summary>
|
||||||
|
public IList<TemplateToken> Defaults
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_defaults == null)
|
||||||
|
{
|
||||||
|
m_defaults = new List<TemplateToken>();
|
||||||
|
}
|
||||||
|
return m_defaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collection of variables associated with the current context.
|
/// Gets the collection of variables associated with the current context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -237,6 +273,21 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the table of files used when parsing the pipeline (e.g. yaml files)
|
||||||
|
/// </summary>
|
||||||
|
public IList<String> FileTable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_fileTable == null)
|
||||||
|
{
|
||||||
|
m_fileTable = new List<String>();
|
||||||
|
}
|
||||||
|
return m_fileTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere
|
// todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere
|
||||||
public void SetJobSidecarContainers(IDictionary<String, String> value)
|
public void SetJobSidecarContainers(IDictionary<String, String> value)
|
||||||
{
|
{
|
||||||
@@ -345,6 +396,16 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
m_environmentVariables = null;
|
m_environmentVariables = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_defaults?.Count == 0)
|
||||||
|
{
|
||||||
|
m_defaults = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fileTable?.Count == 0)
|
||||||
|
{
|
||||||
|
m_fileTable = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_maskHints?.Count == 0)
|
if (m_maskHints?.Count == 0)
|
||||||
{
|
{
|
||||||
m_maskHints = null;
|
m_maskHints = null;
|
||||||
@@ -374,6 +435,12 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
[DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)]
|
[DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)]
|
||||||
private List<TemplateToken> m_environmentVariables;
|
private List<TemplateToken> m_environmentVariables;
|
||||||
|
|
||||||
|
[DataMember(Name = "Defaults", EmitDefaultValue = false)]
|
||||||
|
private List<TemplateToken> m_defaults;
|
||||||
|
|
||||||
|
[DataMember(Name = "FileTable", EmitDefaultValue = false)]
|
||||||
|
private List<String> m_fileTable;
|
||||||
|
|
||||||
[DataMember(Name = "Mask", EmitDefaultValue = false)]
|
[DataMember(Name = "Mask", EmitDefaultValue = false)]
|
||||||
private List<MaskHint> m_maskHints;
|
private List<MaskHint> m_maskHints;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String Clean = "clean";
|
public const String Clean = "clean";
|
||||||
public const String Container = "container";
|
public const String Container = "container";
|
||||||
public const String ContinueOnError = "continue-on-error";
|
public const String ContinueOnError = "continue-on-error";
|
||||||
|
public const String Defaults = "defaults";
|
||||||
public const String Env = "env";
|
public const String Env = "env";
|
||||||
public const String Event = "event";
|
public const String Event = "event";
|
||||||
public const String EventPattern = "github.event";
|
public const String EventPattern = "github.event";
|
||||||
@@ -23,13 +24,18 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String FetchDepth = "fetch-depth";
|
public const String FetchDepth = "fetch-depth";
|
||||||
public const String GeneratedId = "generated-id";
|
public const String GeneratedId = "generated-id";
|
||||||
public const String GitHub = "github";
|
public const String GitHub = "github";
|
||||||
|
public const String HashFiles = "hashFiles";
|
||||||
public const String Id = "id";
|
public const String Id = "id";
|
||||||
public const String If = "if";
|
public const String If = "if";
|
||||||
public const String Image = "image";
|
public const String Image = "image";
|
||||||
public const String Include = "include";
|
public const String Include = "include";
|
||||||
public const String Inputs = "inputs";
|
public const String Inputs = "inputs";
|
||||||
public const String Job = "job";
|
public const String Job = "job";
|
||||||
|
public const String JobDefaultsRun = "job-defaults-run";
|
||||||
|
public const String JobIfResult = "job-if-result";
|
||||||
|
public const String JobOutputs = "job-outputs";
|
||||||
public const String Jobs = "jobs";
|
public const String Jobs = "jobs";
|
||||||
|
public const String Labels = "labels";
|
||||||
public const String Lfs = "lfs";
|
public const String Lfs = "lfs";
|
||||||
public const String Matrix = "matrix";
|
public const String Matrix = "matrix";
|
||||||
public const String MaxParallel = "max-parallel";
|
public const String MaxParallel = "max-parallel";
|
||||||
@@ -56,6 +62,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String Shell = "shell";
|
public const String Shell = "shell";
|
||||||
public const String Skipped = "skipped";
|
public const String Skipped = "skipped";
|
||||||
public const String StepEnv = "step-env";
|
public const String StepEnv = "step-env";
|
||||||
|
public const String StepIfResult = "step-if-result";
|
||||||
public const String Steps = "steps";
|
public const String Steps = "steps";
|
||||||
public const String StepsScopeInputs = "steps-scope-inputs";
|
public const String StepsScopeInputs = "steps-scope-inputs";
|
||||||
public const String StepsScopeOutputs = "steps-scope-outputs";
|
public const String StepsScopeOutputs = "steps-scope-outputs";
|
||||||
|
|||||||
@@ -16,6 +16,20 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
{
|
{
|
||||||
internal static class PipelineTemplateConverter
|
internal static class PipelineTemplateConverter
|
||||||
{
|
{
|
||||||
|
internal static Boolean ConvertToIfResult(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken ifResult)
|
||||||
|
{
|
||||||
|
var expression = ifResult.Traverse().FirstOrDefault(x => x is ExpressionToken);
|
||||||
|
if (expression != null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unexpected type '{expression.GetType().Name}' encountered while reading 'if'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
|
||||||
|
return evaluationResult.IsTruthy;
|
||||||
|
}
|
||||||
|
|
||||||
internal static Boolean? ConvertToStepContinueOnError(
|
internal static Boolean? ConvertToStepContinueOnError(
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
using GitHub.DistributedTask.Expressions2.Sdk.Functions;
|
||||||
using GitHub.DistributedTask.ObjectTemplating;
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
@@ -14,12 +14,16 @@ using ITraceWriter = GitHub.DistributedTask.ObjectTemplating.ITraceWriter;
|
|||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates parts of the workflow DOM. For example, a job strategy or step inputs.
|
||||||
|
/// </summary>
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public class PipelineTemplateEvaluator
|
public class PipelineTemplateEvaluator
|
||||||
{
|
{
|
||||||
public PipelineTemplateEvaluator(
|
public PipelineTemplateEvaluator(
|
||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
TemplateSchema schema)
|
TemplateSchema schema,
|
||||||
|
IList<String> fileTable)
|
||||||
{
|
{
|
||||||
if (!String.Equals(schema.Version, PipelineTemplateConstants.Workflow_1_0, StringComparison.Ordinal))
|
if (!String.Equals(schema.Version, PipelineTemplateConstants.Workflow_1_0, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -28,6 +32,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
m_trace = trace;
|
m_trace = trace;
|
||||||
m_schema = schema;
|
m_schema = schema;
|
||||||
|
m_fileTable = fileTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int32 MaxDepth => 50;
|
public Int32 MaxDepth => 50;
|
||||||
@@ -48,13 +53,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public DictionaryContextData EvaluateStepScopeInputs(
|
public DictionaryContextData EvaluateStepScopeInputs(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(DictionaryContextData);
|
var result = default(DictionaryContextData);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeInputs, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeInputs, token, 0, null, omitHeader: true);
|
||||||
@@ -74,13 +80,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public DictionaryContextData EvaluateStepScopeOutputs(
|
public DictionaryContextData EvaluateStepScopeOutputs(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(DictionaryContextData);
|
var result = default(DictionaryContextData);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeOutputs, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeOutputs, token, 0, null, omitHeader: true);
|
||||||
@@ -100,13 +107,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public Boolean EvaluateStepContinueOnError(
|
public Boolean EvaluateStepContinueOnError(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Boolean?);
|
var result = default(Boolean?);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.BooleanStepsContext, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.BooleanStepsContext, token, 0, null, omitHeader: true);
|
||||||
@@ -124,16 +132,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result ?? false;
|
return result ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String EvaluateStepDisplayName(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
|
{
|
||||||
|
var result = default(String);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = PipelineTemplateConverter.ConvertToStepDisplayName(context, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary<String, String> EvaluateStepEnvironment(
|
public Dictionary<String, String> EvaluateStepEnvironment(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
StringComparer keyComparer)
|
StringComparer keyComparer)
|
||||||
{
|
{
|
||||||
var result = default(Dictionary<String, String>);
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepEnv, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepEnv, token, 0, null, omitHeader: true);
|
||||||
@@ -151,15 +187,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result ?? new Dictionary<String, String>(keyComparer);
|
return result ?? new Dictionary<String, String>(keyComparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean EvaluateStepIf(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
|
IEnumerable<KeyValuePair<String, Object>> expressionState)
|
||||||
|
{
|
||||||
|
var result = default(Boolean?);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions, expressionState);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepIfResult, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = PipelineTemplateConverter.ConvertToIfResult(context, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ?? throw new InvalidOperationException("Step if cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary<String, String> EvaluateStepInputs(
|
public Dictionary<String, String> EvaluateStepInputs(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Dictionary<String, String>);
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepWith, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepWith, token, 0, null, omitHeader: true);
|
||||||
@@ -179,13 +244,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public Int32 EvaluateStepTimeout(
|
public Int32 EvaluateStepTimeout(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Int32?);
|
var result = default(Int32?);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.NumberStepsContext, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.NumberStepsContext, token, 0, null, omitHeader: true);
|
||||||
@@ -205,13 +271,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public JobContainer EvaluateJobContainer(
|
public JobContainer EvaluateJobContainer(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(JobContainer);
|
var result = default(JobContainer);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Container, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Container, token, 0, null, omitHeader: true);
|
||||||
@@ -229,15 +296,90 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Dictionary<String, String> EvaluateJobOutput(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
|
{
|
||||||
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobOutputs, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var mapping = token.AssertMapping("outputs");
|
||||||
|
foreach (var pair in mapping)
|
||||||
|
{
|
||||||
|
// Literal key
|
||||||
|
var key = pair.Key.AssertString("output key");
|
||||||
|
|
||||||
|
// Literal value
|
||||||
|
var value = pair.Value.AssertString("output value");
|
||||||
|
result[key.Value] = value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<String, String> EvaluateJobDefaultsRun(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
|
{
|
||||||
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var mapping = token.AssertMapping("defaults run");
|
||||||
|
foreach (var pair in mapping)
|
||||||
|
{
|
||||||
|
// Literal key
|
||||||
|
var key = pair.Key.AssertString("defaults run key");
|
||||||
|
|
||||||
|
// Literal value
|
||||||
|
var value = pair.Value.AssertString("defaults run value");
|
||||||
|
result[key.Value] = value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
|
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(List<KeyValuePair<String, JobContainer>>);
|
var result = default(List<KeyValuePair<String, JobContainer>>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
|
||||||
@@ -255,62 +397,10 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean TryEvaluateStepDisplayName(
|
private TemplateContext CreateContext(
|
||||||
TemplateToken token,
|
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
out String stepName)
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
{
|
IEnumerable<KeyValuePair<String, Object>> expressionState = null)
|
||||||
stepName = default(String);
|
|
||||||
var context = CreateContext(contextData);
|
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
|
||||||
{
|
|
||||||
// We should only evaluate basic expressions if we are sure we have context on all the Named Values and functions
|
|
||||||
// Otherwise return and use a default name
|
|
||||||
if (token is BasicExpressionToken expressionToken)
|
|
||||||
{
|
|
||||||
ExpressionNode root = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
root = new ExpressionParser().ValidateSyntax(expressionToken.Expression, null) as ExpressionNode;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
context.Errors.Add(exception);
|
|
||||||
context.Errors.Check();
|
|
||||||
}
|
|
||||||
foreach (var node in root.Traverse())
|
|
||||||
{
|
|
||||||
if (node is NamedValue namedValue && !contextData.ContainsKey(namedValue.Name))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (node is Function function &&
|
|
||||||
!context.ExpressionFunctions.Any(item => String.Equals(item.Name, function.Name)) &&
|
|
||||||
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true);
|
|
||||||
context.Errors.Check();
|
|
||||||
stepName = PipelineTemplateConverter.ConvertToStepDisplayName(context, token);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is TemplateValidationException))
|
|
||||||
{
|
|
||||||
context.Errors.Add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Errors.Check();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TemplateContext CreateContext(DictionaryContextData contextData)
|
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -324,6 +414,16 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
TraceWriter = m_trace,
|
TraceWriter = m_trace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add the file table
|
||||||
|
if (m_fileTable?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var file in m_fileTable)
|
||||||
|
{
|
||||||
|
result.GetFileId(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add named values
|
||||||
if (contextData != null)
|
if (contextData != null)
|
||||||
{
|
{
|
||||||
foreach (var pair in contextData)
|
foreach (var pair in contextData)
|
||||||
@@ -332,25 +432,60 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compat for new agent against old server
|
// Add functions
|
||||||
foreach (var name in s_contextNames)
|
var functionNames = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (expressionFunctions?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var function in expressionFunctions)
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(function);
|
||||||
|
functionNames.Add(function.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add missing expression values and expression functions.
|
||||||
|
// This solves the following problems:
|
||||||
|
// - Compat for new agent against old server (new contexts not sent down in job message)
|
||||||
|
// - Evaluating early when all referenced contexts are available, even though all allowed
|
||||||
|
// contexts may not yet be available. For example, evaluating step display name can often
|
||||||
|
// be performed early.
|
||||||
|
foreach (var name in s_expressionValueNames)
|
||||||
{
|
{
|
||||||
if (!result.ExpressionValues.ContainsKey(name))
|
if (!result.ExpressionValues.ContainsKey(name))
|
||||||
{
|
{
|
||||||
result.ExpressionValues[name] = null;
|
result.ExpressionValues[name] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach (var name in s_expressionFunctionNames)
|
||||||
|
{
|
||||||
|
if (!functionNames.Contains(name))
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add state
|
||||||
|
if (expressionState != null)
|
||||||
|
{
|
||||||
|
foreach (var pair in expressionState)
|
||||||
|
{
|
||||||
|
result.State[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ITraceWriter m_trace;
|
private readonly ITraceWriter m_trace;
|
||||||
private readonly TemplateSchema m_schema;
|
private readonly TemplateSchema m_schema;
|
||||||
private readonly String[] s_contextNames = new[]
|
private readonly IList<String> m_fileTable;
|
||||||
|
private readonly String[] s_expressionValueNames = new[]
|
||||||
{
|
{
|
||||||
PipelineTemplateConstants.GitHub,
|
PipelineTemplateConstants.GitHub,
|
||||||
|
PipelineTemplateConstants.Needs,
|
||||||
PipelineTemplateConstants.Strategy,
|
PipelineTemplateConstants.Strategy,
|
||||||
PipelineTemplateConstants.Matrix,
|
PipelineTemplateConstants.Matrix,
|
||||||
|
PipelineTemplateConstants.Needs,
|
||||||
PipelineTemplateConstants.Secrets,
|
PipelineTemplateConstants.Secrets,
|
||||||
PipelineTemplateConstants.Steps,
|
PipelineTemplateConstants.Steps,
|
||||||
PipelineTemplateConstants.Inputs,
|
PipelineTemplateConstants.Inputs,
|
||||||
@@ -358,5 +493,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
PipelineTemplateConstants.Runner,
|
PipelineTemplateConstants.Runner,
|
||||||
PipelineTemplateConstants.Env,
|
PipelineTemplateConstants.Env,
|
||||||
};
|
};
|
||||||
|
private readonly String[] s_expressionFunctionNames = new[]
|
||||||
|
{
|
||||||
|
PipelineTemplateConstants.Always,
|
||||||
|
PipelineTemplateConstants.Cancelled,
|
||||||
|
PipelineTemplateConstants.Failure,
|
||||||
|
PipelineTemplateConstants.HashFiles,
|
||||||
|
PipelineTemplateConstants.Success,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||||
{
|
{
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public sealed class PipelineTemplateSchemaFactory
|
public static class PipelineTemplateSchemaFactory
|
||||||
{
|
{
|
||||||
public TemplateSchema CreateSchema()
|
public static TemplateSchema GetSchema()
|
||||||
|
{
|
||||||
|
if (s_schema == null)
|
||||||
{
|
{
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
var json = default(String);
|
var json = default(String);
|
||||||
@@ -20,7 +24,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
}
|
}
|
||||||
|
|
||||||
var objectReader = new JsonObjectReader(null, json);
|
var objectReader = new JsonObjectReader(null, json);
|
||||||
return TemplateSchema.Load(objectReader);
|
var schema = TemplateSchema.Load(objectReader);
|
||||||
}
|
Interlocked.CompareExchange(ref s_schema, schema, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TemplateSchema s_schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,572 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using YamlDotNet.Core;
|
||||||
|
using YamlDotNet.Core.Events;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a YAML file into a TemplateToken
|
||||||
|
/// </summary>
|
||||||
|
public sealed class YamlObjectReader : IObjectReader
|
||||||
|
{
|
||||||
|
internal YamlObjectReader(
|
||||||
|
Int32? fileId,
|
||||||
|
TextReader input)
|
||||||
|
{
|
||||||
|
m_fileId = fileId;
|
||||||
|
m_parser = new Parser(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean AllowLiteral(out LiteralToken value)
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() is Scalar scalar)
|
||||||
|
{
|
||||||
|
// Tag specified
|
||||||
|
if (!String.IsNullOrEmpty(scalar.Tag))
|
||||||
|
{
|
||||||
|
// String tag
|
||||||
|
if (String.Equals(scalar.Tag, c_stringTag, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not plain style
|
||||||
|
if (scalar.Style != ScalarStyle.Plain)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"The scalar style '{scalar.Style}' on line {scalar.Start.Line} and column {scalar.Start.Column} is not valid with the tag '{scalar.Tag}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean, Float, Integer, or Null
|
||||||
|
switch (scalar.Tag)
|
||||||
|
{
|
||||||
|
case c_booleanTag:
|
||||||
|
value = ParseBoolean(scalar);
|
||||||
|
break;
|
||||||
|
case c_floatTag:
|
||||||
|
value = ParseFloat(scalar);
|
||||||
|
break;
|
||||||
|
case c_integerTag:
|
||||||
|
value = ParseInteger(scalar);
|
||||||
|
break;
|
||||||
|
case c_nullTag:
|
||||||
|
value = ParseNull(scalar);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Unexpected tag '{scalar.Tag}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain style, determine type using YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
||||||
|
if (scalar.Style == ScalarStyle.Plain)
|
||||||
|
{
|
||||||
|
if (MatchNull(scalar, out var nullToken))
|
||||||
|
{
|
||||||
|
value = nullToken;
|
||||||
|
}
|
||||||
|
else if (MatchBoolean(scalar, out var booleanToken))
|
||||||
|
{
|
||||||
|
value = booleanToken;
|
||||||
|
}
|
||||||
|
else if (MatchInteger(scalar, out var numberToken) ||
|
||||||
|
MatchFloat(scalar, out numberToken))
|
||||||
|
{
|
||||||
|
value = numberToken;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise assume string
|
||||||
|
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean AllowSequenceStart(out SequenceToken value)
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() is SequenceStart sequenceStart)
|
||||||
|
{
|
||||||
|
value = new SequenceToken(m_fileId, sequenceStart.Start.Line, sequenceStart.Start.Column);
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean AllowSequenceEnd()
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() is SequenceEnd)
|
||||||
|
{
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean AllowMappingStart(out MappingToken value)
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() is MappingStart mappingStart)
|
||||||
|
{
|
||||||
|
value = new MappingToken(m_fileId, mappingStart.Start.Line, mappingStart.Start.Column);
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean AllowMappingEnd()
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() is MappingEnd)
|
||||||
|
{
|
||||||
|
MoveNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Consumes the last parsing events, which are expected to be DocumentEnd and StreamEnd.
|
||||||
|
/// </summary>
|
||||||
|
public void ValidateEnd()
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() is DocumentEnd)
|
||||||
|
{
|
||||||
|
MoveNext();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected document end parse event");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EvaluateCurrent() is StreamEnd)
|
||||||
|
{
|
||||||
|
MoveNext();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected stream end parse event");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MoveNext())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected end of parse events");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Consumes the first parsing events, which are expected to be StreamStart and DocumentStart.
|
||||||
|
/// </summary>
|
||||||
|
public void ValidateStart()
|
||||||
|
{
|
||||||
|
if (EvaluateCurrent() != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unexpected parser state");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MoveNext())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected a parse event");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EvaluateCurrent() is StreamStart)
|
||||||
|
{
|
||||||
|
MoveNext();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected stream start parse event");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EvaluateCurrent() is DocumentStart)
|
||||||
|
{
|
||||||
|
MoveNext();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected document start parse event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParsingEvent EvaluateCurrent()
|
||||||
|
{
|
||||||
|
if (m_current == null)
|
||||||
|
{
|
||||||
|
m_current = m_parser.Current;
|
||||||
|
if (m_current != null)
|
||||||
|
{
|
||||||
|
if (m_current is Scalar scalar)
|
||||||
|
{
|
||||||
|
// Verify not using achors
|
||||||
|
if (scalar.Anchor != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{scalar.Anchor}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_current is MappingStart mappingStart)
|
||||||
|
{
|
||||||
|
// Verify not using achors
|
||||||
|
if (mappingStart.Anchor != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{mappingStart.Anchor}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_current is SequenceStart sequenceStart)
|
||||||
|
{
|
||||||
|
// Verify not using achors
|
||||||
|
if (sequenceStart.Anchor != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{sequenceStart.Anchor}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!(m_current is MappingEnd) &&
|
||||||
|
!(m_current is SequenceEnd) &&
|
||||||
|
!(m_current is DocumentStart) &&
|
||||||
|
!(m_current is DocumentEnd) &&
|
||||||
|
!(m_current is StreamStart) &&
|
||||||
|
!(m_current is StreamEnd))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Unexpected parsing event type: {m_current.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean MoveNext()
|
||||||
|
{
|
||||||
|
m_current = null;
|
||||||
|
return m_parser.MoveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BooleanToken ParseBoolean(Scalar scalar)
|
||||||
|
{
|
||||||
|
if (MatchBoolean(scalar, out var token))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowInvalidValue(scalar, c_booleanTag); // throws
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NumberToken ParseFloat(Scalar scalar)
|
||||||
|
{
|
||||||
|
if (MatchFloat(scalar, out var token))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowInvalidValue(scalar, c_floatTag); // throws
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NumberToken ParseInteger(Scalar scalar)
|
||||||
|
{
|
||||||
|
if (MatchInteger(scalar, out var token))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowInvalidValue(scalar, c_integerTag); // throws
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NullToken ParseNull(Scalar scalar)
|
||||||
|
{
|
||||||
|
if (MatchNull(scalar, out var token))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowInvalidValue(scalar, c_nullTag); // throws
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean MatchBoolean(
|
||||||
|
Scalar scalar,
|
||||||
|
out BooleanToken value)
|
||||||
|
{
|
||||||
|
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
||||||
|
switch (scalar.Value ?? String.Empty)
|
||||||
|
{
|
||||||
|
case "true":
|
||||||
|
case "True":
|
||||||
|
case "TRUE":
|
||||||
|
value = new BooleanToken(m_fileId, scalar.Start.Line, scalar.Start.Column, true);
|
||||||
|
return true;
|
||||||
|
case "false":
|
||||||
|
case "False":
|
||||||
|
case "FALSE":
|
||||||
|
value = new BooleanToken(m_fileId, scalar.Start.Line, scalar.Start.Column, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean MatchFloat(
|
||||||
|
Scalar scalar,
|
||||||
|
out NumberToken value)
|
||||||
|
{
|
||||||
|
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
||||||
|
var str = scalar.Value;
|
||||||
|
if (!String.IsNullOrEmpty(str))
|
||||||
|
{
|
||||||
|
// Check for [-+]?(\.inf|\.Inf|\.INF)|\.nan|\.NaN|\.NAN
|
||||||
|
switch (str)
|
||||||
|
{
|
||||||
|
case ".inf":
|
||||||
|
case ".Inf":
|
||||||
|
case ".INF":
|
||||||
|
case "+.inf":
|
||||||
|
case "+.Inf":
|
||||||
|
case "+.INF":
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.PositiveInfinity);
|
||||||
|
return true;
|
||||||
|
case "-.inf":
|
||||||
|
case "-.Inf":
|
||||||
|
case "-.INF":
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.NegativeInfinity);
|
||||||
|
return true;
|
||||||
|
case ".nan":
|
||||||
|
case ".NaN":
|
||||||
|
case ".NAN":
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.NaN);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check [-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?
|
||||||
|
|
||||||
|
// Skip leading sign
|
||||||
|
var index = str[0] == '-' || str[0] == '+' ? 1 : 0;
|
||||||
|
|
||||||
|
// Check for integer portion
|
||||||
|
var length = str.Length;
|
||||||
|
var hasInteger = false;
|
||||||
|
while (index < length && str[index] >= '0' && str[index] <= '9')
|
||||||
|
{
|
||||||
|
hasInteger = true;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for decimal point
|
||||||
|
var hasDot = false;
|
||||||
|
if (index < length && str[index] == '.')
|
||||||
|
{
|
||||||
|
hasDot = true;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for decimal portion
|
||||||
|
var hasDecimal = false;
|
||||||
|
while (index < length && str[index] >= '0' && str[index] <= '9')
|
||||||
|
{
|
||||||
|
hasDecimal = true;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check [-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)
|
||||||
|
if ((hasDot && hasDecimal) || hasInteger)
|
||||||
|
{
|
||||||
|
// Check for end
|
||||||
|
if (index == length)
|
||||||
|
{
|
||||||
|
// Try parse
|
||||||
|
if (Double.TryParse(str, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var doubleValue))
|
||||||
|
{
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Otherwise exceeds range
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ThrowInvalidValue(scalar, c_floatTag); // throws
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check [eE][-+]?[0-9]
|
||||||
|
else if (index < length && (str[index] == 'e' || str[index] == 'E'))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// Skip sign
|
||||||
|
if (index < length && (str[index] == '-' || str[index] == '+'))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for exponent
|
||||||
|
var hasExponent = false;
|
||||||
|
while (index < length && str[index] >= '0' && str[index] <= '9')
|
||||||
|
{
|
||||||
|
hasExponent = true;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for end
|
||||||
|
if (hasExponent && index == length)
|
||||||
|
{
|
||||||
|
// Try parse
|
||||||
|
if (Double.TryParse(str, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var doubleValue))
|
||||||
|
{
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, (Double)doubleValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Otherwise exceeds range
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ThrowInvalidValue(scalar, c_floatTag); // throws
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean MatchInteger(
|
||||||
|
Scalar scalar,
|
||||||
|
out NumberToken value)
|
||||||
|
{
|
||||||
|
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
||||||
|
var str = scalar.Value;
|
||||||
|
if (!String.IsNullOrEmpty(str))
|
||||||
|
{
|
||||||
|
// Check for [0-9]+
|
||||||
|
var firstChar = str[0];
|
||||||
|
if (firstChar >= '0' && firstChar <= '9' &&
|
||||||
|
str.Skip(1).All(x => x >= '0' && x <= '9'))
|
||||||
|
{
|
||||||
|
// Try parse
|
||||||
|
if (Double.TryParse(str, NumberStyles.None, CultureInfo.InvariantCulture, out var doubleValue))
|
||||||
|
{
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise exceeds range
|
||||||
|
ThrowInvalidValue(scalar, c_integerTag); // throws
|
||||||
|
}
|
||||||
|
// Check for (-|+)[0-9]+
|
||||||
|
else if ((firstChar == '-' || firstChar == '+') &&
|
||||||
|
str.Length > 1 &&
|
||||||
|
str.Skip(1).All(x => x >= '0' && x <= '9'))
|
||||||
|
{
|
||||||
|
// Try parse
|
||||||
|
if (Double.TryParse(str, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var doubleValue))
|
||||||
|
{
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise exceeds range
|
||||||
|
ThrowInvalidValue(scalar, c_integerTag); // throws
|
||||||
|
}
|
||||||
|
// Check for 0x[0-9a-fA-F]+
|
||||||
|
else if (firstChar == '0' &&
|
||||||
|
str.Length > 2 &&
|
||||||
|
str[1] == 'x' &&
|
||||||
|
str.Skip(2).All(x => (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')))
|
||||||
|
{
|
||||||
|
// Try parse
|
||||||
|
if (Int32.TryParse(str.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var integerValue))
|
||||||
|
{
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, integerValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise exceeds range
|
||||||
|
ThrowInvalidValue(scalar, c_integerTag); // throws
|
||||||
|
}
|
||||||
|
// Check for 0o[0-9]+
|
||||||
|
else if (firstChar == '0' &&
|
||||||
|
str.Length > 2 &&
|
||||||
|
str[1] == 'o' &&
|
||||||
|
str.Skip(2).All(x => x >= '0' && x <= '7'))
|
||||||
|
{
|
||||||
|
// Try parse
|
||||||
|
var integerValue = default(Int32);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
integerValue = Convert.ToInt32(str.Substring(2), 8);
|
||||||
|
}
|
||||||
|
// Otherwise exceeds range
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
ThrowInvalidValue(scalar, c_integerTag); // throws
|
||||||
|
}
|
||||||
|
|
||||||
|
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, integerValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean MatchNull(
|
||||||
|
Scalar scalar,
|
||||||
|
out NullToken value)
|
||||||
|
{
|
||||||
|
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
||||||
|
switch (scalar.Value ?? String.Empty)
|
||||||
|
{
|
||||||
|
case "":
|
||||||
|
case "null":
|
||||||
|
case "Null":
|
||||||
|
case "NULL":
|
||||||
|
case "~":
|
||||||
|
value = new NullToken(m_fileId, scalar.Start.Line, scalar.Start.Column);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowInvalidValue(
|
||||||
|
Scalar scalar,
|
||||||
|
String tag)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"The value '{scalar.Value}' on line {scalar.Start.Line} and column {scalar.Start.Column} is invalid for the type '{scalar.Tag}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private const String c_booleanTag = "tag:yaml.org,2002:bool";
|
||||||
|
private const String c_floatTag = "tag:yaml.org,2002:float";
|
||||||
|
private const String c_integerTag = "tag:yaml.org,2002:int";
|
||||||
|
private const String c_nullTag = "tag:yaml.org,2002:null";
|
||||||
|
private const String c_stringTag = "tag:yaml.org,2002:string";
|
||||||
|
private readonly Int32? m_fileId;
|
||||||
|
private readonly Parser m_parser;
|
||||||
|
private ParsingEvent m_current;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"on": "any",
|
"on": "any",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
|
"defaults": "workflow-defaults",
|
||||||
"env": "workflow-env",
|
"env": "workflow-env",
|
||||||
"jobs": "jobs"
|
"jobs": "jobs"
|
||||||
}
|
}
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
"steps-scope-input-value": {
|
"steps-scope-input-value": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -64,6 +66,7 @@
|
|||||||
"steps-scope-output-value": {
|
"steps-scope-output-value": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"description": "Default input values for a steps template",
|
"description": "Default input values for a steps template",
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
@@ -110,6 +114,7 @@
|
|||||||
"description": "Output values for a steps template",
|
"description": "Output values for a steps template",
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -121,6 +126,23 @@
|
|||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"workflow-defaults": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"run": "workflow-defaults-run"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"workflow-defaults-run": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"shell": "non-empty-string",
|
||||||
|
"working-directory": "non-empty-string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"workflow-env": {
|
"workflow-env": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
@@ -143,16 +165,21 @@
|
|||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"needs": "needs",
|
"needs": "needs",
|
||||||
"if": "string",
|
"if": "job-if",
|
||||||
"strategy": "strategy",
|
"strategy": "strategy",
|
||||||
"name": "string-strategy-context",
|
"name": "string-strategy-context",
|
||||||
"runs-on": "runs-on",
|
"runs-on": {
|
||||||
|
"type": "runs-on",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"timeout-minutes": "number-strategy-context",
|
"timeout-minutes": "number-strategy-context",
|
||||||
"cancel-timeout-minutes": "number-strategy-context",
|
"cancel-timeout-minutes": "number-strategy-context",
|
||||||
"continue-on-error": "boolean",
|
"continue-on-error": "boolean-strategy-context",
|
||||||
"container": "container",
|
"container": "container",
|
||||||
"services": "services",
|
"services": "services",
|
||||||
"env": "job-env",
|
"env": "job-env",
|
||||||
|
"outputs": "job-outputs",
|
||||||
|
"defaults": "job-defaults",
|
||||||
"steps": "steps"
|
"steps": "steps"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,9 +192,41 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"job-if": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,MAX)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,MAX)"
|
||||||
|
],
|
||||||
|
"string": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
"job-if-result": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,MAX)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,MAX)"
|
||||||
|
],
|
||||||
|
"one-of": [
|
||||||
|
"null",
|
||||||
|
"boolean",
|
||||||
|
"number",
|
||||||
|
"string",
|
||||||
|
"sequence",
|
||||||
|
"mapping"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"context": [
|
"context": [
|
||||||
"github"
|
"github",
|
||||||
|
"needs"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -232,25 +291,24 @@
|
|||||||
"runs-on": {
|
"runs-on": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"runs-on-string",
|
"non-empty-string",
|
||||||
|
"sequence-of-non-empty-string",
|
||||||
"runs-on-mapping"
|
"runs-on-mapping"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"runs-on-string": {
|
|
||||||
"string": {
|
|
||||||
"require-non-empty": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"runs-on-mapping": {
|
"runs-on-mapping": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"pool": "non-empty-string"
|
"pool": {
|
||||||
|
"type": "non-empty-string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -258,9 +316,10 @@
|
|||||||
"job-env": {
|
"job-env": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
"secrets",
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix",
|
||||||
|
"secrets"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -268,6 +327,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"job-defaults": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"run": "job-defaults-run"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"job-defaults-run": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"needs",
|
||||||
|
"env"
|
||||||
|
],
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"shell": "non-empty-string",
|
||||||
|
"working-directory": "non-empty-string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"job-outputs": {
|
||||||
|
"mapping": {
|
||||||
|
"loose-key-type": "non-empty-string",
|
||||||
|
"loose-value-type": "string-runner-context"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"steps": {
|
"steps": {
|
||||||
"sequence": {
|
"sequence": {
|
||||||
"item-type": "steps-item"
|
"item-type": "steps-item"
|
||||||
@@ -301,9 +391,12 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": "string-steps-context",
|
"name": "string-steps-context",
|
||||||
"id": "non-empty-string",
|
"id": "non-empty-string",
|
||||||
"if": "string",
|
"if": "step-if",
|
||||||
"timeout-minutes": "number-steps-context",
|
"timeout-minutes": "number-steps-context",
|
||||||
"run": "string-steps-context",
|
"run": {
|
||||||
|
"type": "string-steps-context",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"continue-on-error": "boolean-steps-context",
|
"continue-on-error": "boolean-steps-context",
|
||||||
"env": "step-env",
|
"env": "step-env",
|
||||||
"working-directory": "string-steps-context",
|
"working-directory": "string-steps-context",
|
||||||
@@ -317,9 +410,12 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": "string-steps-context-in-template",
|
"name": "string-steps-context-in-template",
|
||||||
"id": "non-empty-string",
|
"id": "non-empty-string",
|
||||||
"if": "string",
|
"if": "step-if-in-template",
|
||||||
"timeout-minutes": "number-steps-context-in-template",
|
"timeout-minutes": "number-steps-context-in-template",
|
||||||
"run": "string-steps-context-in-template",
|
"run": {
|
||||||
|
"type": "string-steps-context-in-template",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"continue-on-error": "boolean-steps-context-in-template",
|
"continue-on-error": "boolean-steps-context-in-template",
|
||||||
"env": "step-env-in-template",
|
"env": "step-env-in-template",
|
||||||
"working-directory": "string-steps-context-in-template",
|
"working-directory": "string-steps-context-in-template",
|
||||||
@@ -333,10 +429,13 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": "string-steps-context",
|
"name": "string-steps-context",
|
||||||
"id": "non-empty-string",
|
"id": "non-empty-string",
|
||||||
"if": "string",
|
"if": "step-if",
|
||||||
"continue-on-error": "boolean-steps-context",
|
"continue-on-error": "boolean-steps-context",
|
||||||
"timeout-minutes": "number-steps-context",
|
"timeout-minutes": "number-steps-context",
|
||||||
"uses": "non-empty-string",
|
"uses": {
|
||||||
|
"type": "non-empty-string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"with": "step-with",
|
"with": "step-with",
|
||||||
"env": "step-env"
|
"env": "step-env"
|
||||||
}
|
}
|
||||||
@@ -348,16 +447,109 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": "string-steps-context-in-template",
|
"name": "string-steps-context-in-template",
|
||||||
"id": "non-empty-string",
|
"id": "non-empty-string",
|
||||||
"if": "string",
|
"if": "step-if-in-template",
|
||||||
"continue-on-error": "boolean-steps-context-in-template",
|
"continue-on-error": "boolean-steps-context-in-template",
|
||||||
"timeout-minutes": "number-steps-context-in-template",
|
"timeout-minutes": "number-steps-context-in-template",
|
||||||
"uses": "non-empty-string",
|
"uses": {
|
||||||
|
"type": "non-empty-string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"with": "step-with-in-template",
|
"with": "step-with-in-template",
|
||||||
"env": "step-env-in-template"
|
"env": "step-env-in-template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"step-if": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"string": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
"step-if-in-template": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"string": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
"step-if-result": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"one-of": [
|
||||||
|
"null",
|
||||||
|
"boolean",
|
||||||
|
"number",
|
||||||
|
"string",
|
||||||
|
"sequence",
|
||||||
|
"mapping"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"step-if-result-in-template": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"one-of": [
|
||||||
|
"null",
|
||||||
|
"boolean",
|
||||||
|
"number",
|
||||||
|
"string",
|
||||||
|
"sequence",
|
||||||
|
"mapping"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
"steps-template-reference": {
|
"steps-template-reference": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -381,6 +573,7 @@
|
|||||||
"steps-template-reference-inputs": {
|
"steps-template-reference-inputs": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -398,6 +591,7 @@
|
|||||||
"steps-template-reference-inputs-in-template": {
|
"steps-template-reference-inputs-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -416,13 +610,15 @@
|
|||||||
"step-env": {
|
"step-env": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -433,6 +629,7 @@
|
|||||||
"step-env-in-template": {
|
"step-env-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -440,7 +637,8 @@
|
|||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -451,13 +649,35 @@
|
|||||||
"step-with": {
|
"step-with": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"mapping": {
|
||||||
|
"loose-key-type": "non-empty-string",
|
||||||
|
"loose-value-type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"step-with-in-template": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"secrets",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -468,6 +688,7 @@
|
|||||||
"container": {
|
"container": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
@@ -492,6 +713,7 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
@@ -504,6 +726,7 @@
|
|||||||
"services-container": {
|
"services-container": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
@@ -520,24 +743,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"step-with-in-template": {
|
|
||||||
"context": [
|
|
||||||
"github",
|
|
||||||
"strategy",
|
|
||||||
"matrix",
|
|
||||||
"secrets",
|
|
||||||
"steps",
|
|
||||||
"inputs",
|
|
||||||
"job",
|
|
||||||
"runner",
|
|
||||||
"env"
|
|
||||||
],
|
|
||||||
"mapping": {
|
|
||||||
"loose-key-type": "non-empty-string",
|
|
||||||
"loose-value-type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"non-empty-string": {
|
"non-empty-string": {
|
||||||
"string": {
|
"string": {
|
||||||
"require-non-empty": true
|
"require-non-empty": true
|
||||||
@@ -550,9 +755,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"boolean-strategy-context": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix"
|
||||||
|
],
|
||||||
|
"boolean": {}
|
||||||
|
},
|
||||||
|
|
||||||
"number-strategy-context": {
|
"number-strategy-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
@@ -562,6 +778,7 @@
|
|||||||
"string-strategy-context": {
|
"string-strategy-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix"
|
"matrix"
|
||||||
],
|
],
|
||||||
@@ -571,13 +788,15 @@
|
|||||||
"boolean-steps-context": {
|
"boolean-steps-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"boolean": {}
|
"boolean": {}
|
||||||
},
|
},
|
||||||
@@ -585,6 +804,7 @@
|
|||||||
"boolean-steps-context-in-template": {
|
"boolean-steps-context-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -592,7 +812,8 @@
|
|||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"boolean": {}
|
"boolean": {}
|
||||||
},
|
},
|
||||||
@@ -600,13 +821,15 @@
|
|||||||
"number-steps-context": {
|
"number-steps-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"number": {}
|
"number": {}
|
||||||
},
|
},
|
||||||
@@ -614,6 +837,7 @@
|
|||||||
"number-steps-context-in-template": {
|
"number-steps-context-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -621,14 +845,16 @@
|
|||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"number": {}
|
"number": {}
|
||||||
},
|
},
|
||||||
|
|
||||||
"string-steps-context": {
|
"string-runner-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -640,9 +866,26 @@
|
|||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"string-steps-context": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"secrets",
|
||||||
|
"steps",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"string": {}
|
||||||
|
},
|
||||||
|
|
||||||
"string-steps-context-in-template": {
|
"string-steps-context-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -650,7 +893,8 @@
|
|||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,6 @@ using System.Runtime.Serialization;
|
|||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
{
|
{
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
public class GitHubToken
|
|
||||||
{
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string Token { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public DateTime Expires_at { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public class Issue
|
public class Issue
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -123,11 +123,12 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
Int64 requestId,
|
Int64 requestId,
|
||||||
Guid jobId,
|
Guid jobId,
|
||||||
TaskResult result,
|
TaskResult result,
|
||||||
IDictionary<String, VariableValue> outputVariables)
|
Dictionary<String, VariableValue> outputs)
|
||||||
: base(JobEventTypes.JobCompleted, jobId)
|
: base(JobEventTypes.JobCompleted, jobId)
|
||||||
{
|
{
|
||||||
this.RequestId = requestId;
|
this.RequestId = requestId;
|
||||||
this.Result = result;
|
this.Result = result;
|
||||||
|
this.Outputs = outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
@@ -143,6 +144,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public IDictionary<String, VariableValue> Outputs
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
|
|||||||
@@ -260,5 +260,8 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public static readonly Guid CheckpointResourcesLocationId = new Guid(CheckpointResourcesLocationIdString);
|
public static readonly Guid CheckpointResourcesLocationId = new Guid(CheckpointResourcesLocationIdString);
|
||||||
public const String CheckpointResourcesResource = "references";
|
public const String CheckpointResourcesResource = "references";
|
||||||
|
|
||||||
|
public static readonly Guid RunnerAuthUrl = new Guid("{A82A119C-1E46-44B6-8D75-C82A79CF975B}");
|
||||||
|
public const string RunnerAuthUrlResource = "authurl";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
||||||
TimelineReference timeline = null;
|
TimelineReference timeline = null;
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", 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);
|
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", 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);
|
||||||
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
||||||
TimelineReference timeline = null;
|
TimelineReference timeline = null;
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", 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);
|
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JobCancelMessage CreateJobCancelMessage()
|
private JobCancelMessage CreateJobCancelMessage()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Xunit;
|
|||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests
|
namespace GitHub.Runner.Common.Tests
|
||||||
{
|
{
|
||||||
@@ -81,6 +82,102 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public async Task SetCIEnv()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
var existingCI = Environment.GetEnvironmentVariable("CI");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Clear out CI and make sure process invoker sets it.
|
||||||
|
Environment.SetEnvironmentVariable("CI", null);
|
||||||
|
|
||||||
|
Tracing trace = hc.GetTrace();
|
||||||
|
|
||||||
|
Int32 exitCode = -1;
|
||||||
|
var processInvoker = new ProcessInvokerWrapper();
|
||||||
|
processInvoker.Initialize(hc);
|
||||||
|
var stdout = new List<string>();
|
||||||
|
var stderr = new List<string>();
|
||||||
|
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
||||||
|
{
|
||||||
|
trace.Info(e.Data);
|
||||||
|
stdout.Add(e.Data);
|
||||||
|
};
|
||||||
|
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
||||||
|
{
|
||||||
|
trace.Info(e.Data);
|
||||||
|
stderr.Add(e.Data);
|
||||||
|
};
|
||||||
|
#if OS_WINDOWS
|
||||||
|
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %CI%\"", null, CancellationToken.None);
|
||||||
|
#else
|
||||||
|
exitCode = await processInvoker.ExecuteAsync("", "bash", "-c \"echo $CI\"", null, CancellationToken.None);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
trace.Info("Exit Code: {0}", exitCode);
|
||||||
|
Assert.Equal(0, exitCode);
|
||||||
|
|
||||||
|
Assert.Equal("true", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("CI", existingCI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public async Task KeepExistingCIEnv()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
var existingCI = Environment.GetEnvironmentVariable("CI");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Clear out CI and make sure process invoker sets it.
|
||||||
|
Environment.SetEnvironmentVariable("CI", null);
|
||||||
|
|
||||||
|
Tracing trace = hc.GetTrace();
|
||||||
|
|
||||||
|
Int32 exitCode = -1;
|
||||||
|
var processInvoker = new ProcessInvokerWrapper();
|
||||||
|
processInvoker.Initialize(hc);
|
||||||
|
var stdout = new List<string>();
|
||||||
|
var stderr = new List<string>();
|
||||||
|
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
||||||
|
{
|
||||||
|
trace.Info(e.Data);
|
||||||
|
stdout.Add(e.Data);
|
||||||
|
};
|
||||||
|
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
||||||
|
{
|
||||||
|
trace.Info(e.Data);
|
||||||
|
stderr.Add(e.Data);
|
||||||
|
};
|
||||||
|
#if OS_WINDOWS
|
||||||
|
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %CI%\"", new Dictionary<string, string>() { { "CI", "false" } }, CancellationToken.None);
|
||||||
|
#else
|
||||||
|
exitCode = await processInvoker.ExecuteAsync("", "bash", "-c \"echo $CI\"", new Dictionary<string, string>() { { "CI", "false" } }, CancellationToken.None);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
trace.Info("Exit Code: {0}", exitCode);
|
||||||
|
Assert.Equal(0, exitCode);
|
||||||
|
|
||||||
|
Assert.Equal("false", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("CI", existingCI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if !OS_WINDOWS
|
#if !OS_WINDOWS
|
||||||
//Run a process that normally takes 20sec to finish and cancel it.
|
//Run a process that normally takes 20sec to finish and cancel it.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -279,6 +279,13 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
".options");
|
".options");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.SetupInfo:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".setup_info");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
TimelineReference timeline = new TimelineReference();
|
TimelineReference timeline = new TimelineReference();
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
string jobName = "some job name";
|
string jobName = "some job name";
|
||||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
||||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||||
{
|
{
|
||||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
@@ -54,7 +56,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
@@ -164,7 +166,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.True(steps.Count == 0);
|
Assert.True(steps.Count == 0);
|
||||||
}
|
}
|
||||||
@@ -203,7 +205,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfile");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfile");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
Assert.Equal(Path.Combine(actionDir, "Dockerfile"), (steps[0].Data as ContainerSetupInfo).Container.Dockerfile);
|
Assert.Equal(Path.Combine(actionDir, "Dockerfile"), (steps[0].Data as ContainerSetupInfo).Container.Dockerfile);
|
||||||
@@ -243,7 +245,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -282,7 +284,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -322,7 +324,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerfileRelativePath");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerfileRelativePath");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -362,7 +364,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerHubImage");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerHubImage");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
||||||
@@ -401,7 +403,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionYamlFile_DockerHubImage");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionYamlFile_DockerHubImage");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.Equal((steps[0].Data as ContainerSetupInfo).StepIds[0], actionId);
|
Assert.Equal((steps[0].Data as ContainerSetupInfo).StepIds[0], actionId);
|
||||||
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
||||||
@@ -440,7 +442,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithactionfileanddockerfile");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithactionfileanddockerfile");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -557,7 +559,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal(actionId1, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId1, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
@@ -618,7 +620,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
// node.js based action doesn't need any extra steps to build/pull containers.
|
// node.js based action doesn't need any extra steps to build/pull containers.
|
||||||
Assert.True(steps.Count == 0);
|
Assert.True(steps.Count == 0);
|
||||||
@@ -629,47 +631,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public async void PrepareActions_RepositoryActionWithInvalidWrapperActionfile_Node()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
Setup();
|
|
||||||
var actionId = Guid.NewGuid();
|
|
||||||
var actions = new List<Pipelines.ActionStep>
|
|
||||||
{
|
|
||||||
new Pipelines.ActionStep()
|
|
||||||
{
|
|
||||||
Name = "action",
|
|
||||||
Id = actionId,
|
|
||||||
Reference = new Pipelines.RepositoryPathReference()
|
|
||||||
{
|
|
||||||
Name = "TingluoHuang/runner_L0",
|
|
||||||
Ref = "RepositoryActionWithInvalidWrapperActionfile_Node",
|
|
||||||
RepositoryType = "GitHub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Act
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
|
||||||
}
|
|
||||||
catch (ArgumentNullException e)
|
|
||||||
{
|
|
||||||
Assert.Contains("Entry javascript fils is not provided.", e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -1641,6 +1602,8 @@ runs:
|
|||||||
_ec = new Mock<IExecutionContext>();
|
_ec = new Mock<IExecutionContext>();
|
||||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -63,52 +65,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void Load_ContainerAction_Dockerfile_Pre()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
Setup();
|
|
||||||
|
|
||||||
var actionManifest = new ActionManifestManager();
|
|
||||||
actionManifest.Initialize(_hc);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_init.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(ActionExecutionType.Container, result.Execution.ExecutionType);
|
|
||||||
|
|
||||||
var containerAction = result.Execution as ContainerActionExecutionData;
|
|
||||||
|
|
||||||
Assert.Equal("Dockerfile", containerAction.Image);
|
|
||||||
Assert.Equal("main.sh", containerAction.EntryPoint);
|
|
||||||
Assert.Equal("init.sh", containerAction.Init);
|
|
||||||
Assert.Equal("success()", containerAction.InitCondition);
|
|
||||||
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
|
||||||
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
|
||||||
Assert.Equal("foo", containerAction.Environment[0].Value.ToString());
|
|
||||||
Assert.Equal("Url", containerAction.Environment[1].Key.ToString());
|
|
||||||
Assert.Equal("bar", containerAction.Environment[1].Value.ToString());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -155,52 +111,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void Load_ContainerAction_Dockerfile_Pre_DefaultCondition()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
Setup();
|
|
||||||
|
|
||||||
var actionManifest = new ActionManifestManager();
|
|
||||||
actionManifest.Initialize(_hc);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_init_default.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(ActionExecutionType.Container, result.Execution.ExecutionType);
|
|
||||||
|
|
||||||
var containerAction = result.Execution as ContainerActionExecutionData;
|
|
||||||
|
|
||||||
Assert.Equal("Dockerfile", containerAction.Image);
|
|
||||||
Assert.Equal("main.sh", containerAction.EntryPoint);
|
|
||||||
Assert.Equal("init.sh", containerAction.Init);
|
|
||||||
Assert.Equal("always()", containerAction.InitCondition);
|
|
||||||
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
|
||||||
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
|
||||||
Assert.Equal("foo", containerAction.Environment[0].Value.ToString());
|
|
||||||
Assert.Equal("Url", containerAction.Environment[1].Key.ToString());
|
|
||||||
Assert.Equal("bar", containerAction.Environment[1].Value.ToString());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -413,94 +323,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void Load_NodeAction_Pre()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
Setup();
|
|
||||||
|
|
||||||
var actionManifest = new ActionManifestManager();
|
|
||||||
actionManifest.Initialize(_hc);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_init.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("init.js", nodeAction.Init);
|
|
||||||
Assert.Equal("cancelled()", nodeAction.InitCondition);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void Load_NodeAction_Init_DefaultCondition()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
Setup();
|
|
||||||
|
|
||||||
var actionManifest = new ActionManifestManager();
|
|
||||||
actionManifest.Initialize(_hc);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_init_default.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("init.js", nodeAction.Init);
|
|
||||||
Assert.Equal("always()", nodeAction.InitCondition);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -713,26 +535,26 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionManifest = new ActionManifestManager();
|
var actionManifest = new ActionManifestManager();
|
||||||
actionManifest.Initialize(_hc);
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
var githubContext = new DictionaryContextData();
|
_ec.Object.ExpressionValues["github"] = new DictionaryContextData
|
||||||
githubContext.Add("ref", new StringContextData("refs/heads/master"));
|
{
|
||||||
|
{ "ref", new StringContextData("refs/heads/master") },
|
||||||
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
};
|
||||||
evaluateContext["github"] = githubContext;
|
_ec.Object.ExpressionValues["strategy"] = new DictionaryContextData();
|
||||||
evaluateContext["strategy"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["matrix"] = new DictionaryContextData();
|
||||||
evaluateContext["matrix"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["steps"] = new DictionaryContextData();
|
||||||
evaluateContext["steps"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["job"] = new DictionaryContextData();
|
||||||
evaluateContext["job"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["runner"] = new DictionaryContextData();
|
||||||
evaluateContext["runner"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["env"] = new DictionaryContextData();
|
||||||
evaluateContext["env"] = new DictionaryContextData();
|
_ec.Object.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>("hashFiles", 1, 255));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"), evaluateContext);
|
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"));
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal("defaultValue", result);
|
Assert.Equal("defaultValue", result);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"), evaluateContext);
|
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"));
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal("refs/heads/master", result);
|
Assert.Equal("refs/heads/master", result);
|
||||||
@@ -755,6 +577,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.WriteDebug).Returns(true);
|
_ec.Setup(x => x.WriteDebug).Returns(true);
|
||||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
@@ -322,6 +323,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec = new Mock<IExecutionContext>();
|
_ec = new Mock<IExecutionContext>();
|
||||||
_ec.Setup(x => x.ExpressionValues).Returns(_context);
|
_ec.Setup(x => x.ExpressionValues).Returns(_context);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
|
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
|
||||||
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
|
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
|
||||||
_ec.Setup(x => x.SetGitHubContext(It.IsAny<string>(), It.IsAny<string>()));
|
_ec.Setup(x => x.SetGitHubContext(It.IsAny<string>(), It.IsAny<string>()));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
TimelineReference timeline = new TimelineReference();
|
TimelineReference timeline = new TimelineReference();
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
string jobName = "some job name";
|
string jobName = "some job name";
|
||||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
||||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||||
{
|
{
|
||||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||||
@@ -101,7 +101,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
TimelineReference timeline = new TimelineReference();
|
TimelineReference timeline = new TimelineReference();
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
string jobName = "some job name";
|
string jobName = "some job name";
|
||||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
||||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||||
{
|
{
|
||||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||||
@@ -152,7 +152,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
TimelineReference timeline = new TimelineReference();
|
TimelineReference timeline = new TimelineReference();
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
string jobName = "some job name";
|
string jobName = "some job name";
|
||||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, 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);
|
||||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||||
{
|
{
|
||||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||||
@@ -199,20 +199,20 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
|
|
||||||
var postRunner1 = hc.CreateService<IActionRunner>();
|
var postRunner1 = hc.CreateService<IActionRunner>();
|
||||||
postRunner1.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
postRunner1.Action = new Pipelines.ActionStep() { Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
||||||
postRunner1.Stage = ActionRunStage.Post;
|
postRunner1.Stage = ActionRunStage.Post;
|
||||||
postRunner1.Condition = "always()";
|
postRunner1.Condition = "always()";
|
||||||
postRunner1.DisplayName = "post1";
|
postRunner1.DisplayName = "post1";
|
||||||
|
|
||||||
|
|
||||||
var postRunner2 = hc.CreateService<IActionRunner>();
|
var postRunner2 = hc.CreateService<IActionRunner>();
|
||||||
postRunner2.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
postRunner2.Action = new Pipelines.ActionStep() { Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
||||||
postRunner2.Stage = ActionRunStage.Post;
|
postRunner2.Stage = ActionRunStage.Post;
|
||||||
postRunner2.Condition = "always()";
|
postRunner2.Condition = "always()";
|
||||||
postRunner2.DisplayName = "post2";
|
postRunner2.DisplayName = "post2";
|
||||||
|
|
||||||
action1.RegisterPostJobStep(postRunner1);
|
action1.RegisterPostJobStep("post1", postRunner1);
|
||||||
action2.RegisterPostJobStep(postRunner2);
|
action2.RegisterPostJobStep("post2", postRunner2);
|
||||||
|
|
||||||
Assert.NotNull(jobContext.JobSteps);
|
Assert.NotNull(jobContext.JobSteps);
|
||||||
Assert.NotNull(jobContext.PostJobSteps);
|
Assert.NotNull(jobContext.PostJobSteps);
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Worker
|
namespace GitHub.Runner.Common.Tests.Worker.Expressions
|
||||||
{
|
{
|
||||||
public sealed class ExpressionManagerL0
|
public sealed class ConditionFunctionsL0
|
||||||
{
|
{
|
||||||
private Mock<IExecutionContext> _ec;
|
private TemplateContext _templateContext;
|
||||||
private ExpressionManager _expressionManager;
|
|
||||||
private DictionaryContextData _expressions;
|
|
||||||
private JobContext _jobContext;
|
private JobContext _jobContext;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "always()").Value;
|
bool actual = Evaluate("always()");
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "cancelled()").Value;
|
bool actual = Evaluate("cancelled()");
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -97,7 +97,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "failure()").Value;
|
bool actual = Evaluate("failure()");
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -126,37 +126,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "success()").Value;
|
bool actual = Evaluate("success()");
|
||||||
|
|
||||||
// Assert.
|
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void ContextNamedValue()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = CreateTestContext())
|
|
||||||
{
|
|
||||||
// Arrange.
|
|
||||||
var variableSets = new[]
|
|
||||||
{
|
|
||||||
new { Condition = "github.ref == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
|
|
||||||
new { Condition = "github['ref'] == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
|
|
||||||
new { Condition = "github.nosuch || '' == ''", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
|
|
||||||
new { Condition = "github['ref'] == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false },
|
|
||||||
new { Condition = "github.ref == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false },
|
|
||||||
};
|
|
||||||
foreach (var variableSet in variableSets)
|
|
||||||
{
|
|
||||||
InitializeExecutionContext(hc);
|
|
||||||
_ec.Object.ExpressionValues["github"] = new GitHubContext() { { variableSet.VariableName, new StringContextData(variableSet.VariableValue) } };
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, variableSet.Condition).Value;
|
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -166,21 +136,34 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
var hc = new TestHostContext(this, testName);
|
return new TestHostContext(this, testName);
|
||||||
_expressionManager = new ExpressionManager();
|
|
||||||
_expressionManager.Initialize(hc);
|
|
||||||
return hc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeExecutionContext(TestHostContext hc)
|
private void InitializeExecutionContext(TestHostContext hc)
|
||||||
{
|
{
|
||||||
_expressions = new DictionaryContextData();
|
|
||||||
_jobContext = new JobContext();
|
_jobContext = new JobContext();
|
||||||
|
|
||||||
_ec = new Mock<IExecutionContext>();
|
var executionContext = new Mock<IExecutionContext>();
|
||||||
_ec.SetupAllProperties();
|
executionContext.SetupAllProperties();
|
||||||
_ec.Setup(x => x.ExpressionValues).Returns(_expressions);
|
executionContext.Setup(x => x.JobContext).Returns(_jobContext);
|
||||||
_ec.Setup(x => x.JobContext).Returns(_jobContext);
|
|
||||||
|
_templateContext = new TemplateContext();
|
||||||
|
_templateContext.State[nameof(IExecutionContext)] = executionContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Evaluate(string expression)
|
||||||
|
{
|
||||||
|
var parser = new ExpressionParser();
|
||||||
|
var functions = new IFunctionInfo[]
|
||||||
|
{
|
||||||
|
new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0),
|
||||||
|
new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0),
|
||||||
|
new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0),
|
||||||
|
new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0),
|
||||||
|
};
|
||||||
|
var tree = parser.CreateTree(expression, null, null, functions);
|
||||||
|
var result = tree.Evaluate(null, null, _templateContext, null);
|
||||||
|
return result.IsTruthy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
private Mock<IJobServerQueue> _jobServerQueue;
|
private Mock<IJobServerQueue> _jobServerQueue;
|
||||||
private Mock<IConfigurationStore> _config;
|
private Mock<IConfigurationStore> _config;
|
||||||
private Mock<IPagingLogger> _logger;
|
private Mock<IPagingLogger> _logger;
|
||||||
private Mock<IExpressionManager> _express;
|
|
||||||
private Mock<IContainerOperationProvider> _containerProvider;
|
private Mock<IContainerOperationProvider> _containerProvider;
|
||||||
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
|
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobServerQueue = new Mock<IJobServerQueue>();
|
_jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
_config = new Mock<IConfigurationStore>();
|
_config = new Mock<IConfigurationStore>();
|
||||||
_logger = new Mock<IPagingLogger>();
|
_logger = new Mock<IPagingLogger>();
|
||||||
_express = new Mock<IExpressionManager>();
|
|
||||||
_containerProvider = new Mock<IContainerOperationProvider>();
|
_containerProvider = new Mock<IContainerOperationProvider>();
|
||||||
_diagnosticLogManager = new Mock<IDiagnosticLogManager>();
|
_diagnosticLogManager = new Mock<IDiagnosticLogManager>();
|
||||||
_directoryManager = new Mock<IPipelineDirectoryManager>();
|
_directoryManager = new Mock<IPipelineDirectoryManager>();
|
||||||
@@ -100,7 +98,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
_message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), steps, null);
|
_message = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), steps, null, null, null, null);
|
||||||
GitHubContext github = new GitHubContext();
|
GitHubContext github = new GitHubContext();
|
||||||
github["repository"] = new Pipelines.ContextData.StringContextData("actions/runner");
|
github["repository"] = new Pipelines.ContextData.StringContextData("actions/runner");
|
||||||
_message.ContextData.Add("github", github);
|
_message.ContextData.Add("github", github);
|
||||||
@@ -108,7 +106,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.SetSingleton(_actionManager.Object);
|
hc.SetSingleton(_actionManager.Object);
|
||||||
hc.SetSingleton(_config.Object);
|
hc.SetSingleton(_config.Object);
|
||||||
hc.SetSingleton(_jobServerQueue.Object);
|
hc.SetSingleton(_jobServerQueue.Object);
|
||||||
hc.SetSingleton(_express.Object);
|
|
||||||
hc.SetSingleton(_containerProvider.Object);
|
hc.SetSingleton(_containerProvider.Object);
|
||||||
hc.SetSingleton(_directoryManager.Object);
|
hc.SetSingleton(_directoryManager.Object);
|
||||||
hc.SetSingleton(_diagnosticLogManager.Object);
|
hc.SetSingleton(_diagnosticLogManager.Object);
|
||||||
@@ -144,7 +141,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
jobExtension.Initialize(hc);
|
jobExtension.Initialize(hc);
|
||||||
|
|
||||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
||||||
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>(), new Dictionary<Guid, IActionRunner>())));
|
.Returns(Task.FromResult(new List<JobExtensionRunner>()));
|
||||||
|
|
||||||
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
||||||
|
|
||||||
@@ -179,7 +176,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
jobExtension.Initialize(hc);
|
jobExtension.Initialize(hc);
|
||||||
|
|
||||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
||||||
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }, new Dictionary<Guid, IActionRunner>())));
|
.Returns(Task.FromResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }));
|
||||||
|
|
||||||
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
var expressionManager = new ExpressionManager();
|
|
||||||
expressionManager.Initialize(hc);
|
|
||||||
hc.SetSingleton<IExpressionManager>(expressionManager);
|
|
||||||
|
|
||||||
_jobRunner = new JobRunner();
|
_jobRunner = new JobRunner();
|
||||||
_jobRunner.Initialize(hc);
|
_jobRunner.Initialize(hc);
|
||||||
@@ -63,7 +60,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
||||||
TimelineReference timeline = new Timeline(Guid.NewGuid());
|
TimelineReference timeline = new Timeline(Guid.NewGuid());
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
_message = new Pipelines.AgentJobRequestMessage(plan, 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);
|
_message = new Pipelines.AgentJobRequestMessage(plan, 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);
|
||||||
_message.Variables[Constants.Variables.System.Culture] = "en-US";
|
_message.Variables[Constants.Variables.System.Culture] = "en-US";
|
||||||
_message.Resources.Endpoints.Add(new ServiceEndpoint()
|
_message.Resources.Endpoints.Add(new ServiceEndpoint()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using System;
|
||||||
using GitHub.Runner.Worker;
|
|
||||||
using Moq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Worker
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
{
|
{
|
||||||
@@ -26,9 +27,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
var hc = new TestHostContext(this, testName);
|
var hc = new TestHostContext(this, testName);
|
||||||
var expressionManager = new ExpressionManager();
|
|
||||||
expressionManager.Initialize(hc);
|
|
||||||
hc.SetSingleton<IExpressionManager>(expressionManager);
|
|
||||||
Dictionary<string, VariableValue> variablesToCopy = new Dictionary<string, VariableValue>();
|
Dictionary<string, VariableValue> variablesToCopy = new Dictionary<string, VariableValue>();
|
||||||
_variables = new Variables(
|
_variables = new Variables(
|
||||||
hostContext: hc,
|
hostContext: hc,
|
||||||
@@ -48,6 +46,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_contexts["runner"] = new DictionaryContextData();
|
_contexts["runner"] = new DictionaryContextData();
|
||||||
_contexts["job"] = _jobContext;
|
_contexts["job"] = _jobContext;
|
||||||
_ec.Setup(x => x.ExpressionValues).Returns(_contexts);
|
_ec.Setup(x => x.ExpressionValues).Returns(_contexts);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.JobContext).Returns(_jobContext);
|
_ec.Setup(x => x.JobContext).Returns(_jobContext);
|
||||||
|
|
||||||
_stepContext = new StepsContext();
|
_stepContext = new StepsContext();
|
||||||
@@ -55,6 +54,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec.Setup(x => x.PostJobSteps).Returns(new Stack<IStep>());
|
_ec.Setup(x => x.PostJobSteps).Returns(new Stack<IStep>());
|
||||||
|
|
||||||
|
var trace = hc.GetTrace();
|
||||||
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
|
||||||
|
|
||||||
_stepsRunner = new StepsRunner();
|
_stepsRunner = new StepsRunner();
|
||||||
_stepsRunner.Initialize(hc);
|
_stepsRunner.Initialize(hc);
|
||||||
return hc;
|
return hc;
|
||||||
@@ -379,16 +381,11 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
using (TestHostContext hc = CreateTestContext())
|
using (TestHostContext hc = CreateTestContext())
|
||||||
{
|
{
|
||||||
var expressionManager = new Mock<IExpressionManager>();
|
|
||||||
expressionManager.Object.Initialize(hc);
|
|
||||||
hc.SetSingleton<IExpressionManager>(expressionManager.Object);
|
|
||||||
expressionManager.Setup(x => x.Evaluate(It.IsAny<IExecutionContext>(), It.IsAny<string>(), It.IsAny<bool>())).Throws(new Exception());
|
|
||||||
|
|
||||||
// Arrange.
|
// Arrange.
|
||||||
var variableSets = new[]
|
var variableSets = new[]
|
||||||
{
|
{
|
||||||
new[] { CreateStep(hc, TaskResult.Succeeded, "success()") },
|
new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") },
|
||||||
new[] { CreateStep(hc, TaskResult.Succeeded, "success()") },
|
new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") },
|
||||||
};
|
};
|
||||||
foreach (var variableSet in variableSets)
|
foreach (var variableSet in variableSets)
|
||||||
{
|
{
|
||||||
@@ -513,7 +510,78 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mock<IActionRunner> CreateStep(TestHostContext hc, TaskResult result, string condition, Boolean continueOnError = false, MappingToken env = null, string name = "Test", bool setOutput = false)
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async Task StepContextOutcome()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var step1 = CreateStep(hc, TaskResult.Succeeded, "success()", contextName: "step1");
|
||||||
|
var step2 = CreateStep(hc, TaskResult.Failed, "steps.step1.outcome == 'success'", continueOnError: true, contextName: "step2");
|
||||||
|
var step3 = CreateStep(hc, TaskResult.Succeeded, "steps.step1.outcome == 'success' && steps.step2.outcome == 'failure'", contextName: "step3");
|
||||||
|
|
||||||
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
|
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
|
||||||
|
|
||||||
|
step1.Verify(x => x.RunAsync(), Times.Once);
|
||||||
|
step2.Verify(x => x.RunAsync(), Times.Once);
|
||||||
|
step3.Verify(x => x.RunAsync(), Times.Once);
|
||||||
|
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Failed.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString(""));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async Task StepContextConclusion()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var step1 = CreateStep(hc, TaskResult.Succeeded, "false", contextName: "step1");
|
||||||
|
var step2 = CreateStep(hc, TaskResult.Failed, "steps.step1.conclusion == 'skipped'", continueOnError: true, contextName: "step2");
|
||||||
|
var step3 = CreateStep(hc, TaskResult.Succeeded, "steps.step1.outcome == 'skipped' && steps.step2.outcome == 'failure' && steps.step2.conclusion == 'success'", contextName: "step3");
|
||||||
|
|
||||||
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
|
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
|
||||||
|
|
||||||
|
step1.Verify(x => x.RunAsync(), Times.Never);
|
||||||
|
step2.Verify(x => x.RunAsync(), Times.Once);
|
||||||
|
step3.Verify(x => x.RunAsync(), Times.Once);
|
||||||
|
|
||||||
|
Assert.Equal(TaskResult.Skipped.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Skipped.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Failed.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString(""));
|
||||||
|
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<IActionRunner> CreateStep(TestHostContext hc, TaskResult result, string condition, Boolean continueOnError = false, MappingToken env = null, string name = "Test", bool setOutput = false, string contextName = null)
|
||||||
{
|
{
|
||||||
// Setup the step.
|
// Setup the step.
|
||||||
var step = new Mock<IActionRunner>();
|
var step = new Mock<IActionRunner>();
|
||||||
@@ -524,7 +592,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
Environment = env
|
Environment = env,
|
||||||
|
ContextName = contextName ?? "Test"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup the step execution context.
|
// Setup the step execution context.
|
||||||
@@ -534,8 +603,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
stepContext.Setup(x => x.Variables).Returns(_variables);
|
stepContext.Setup(x => x.Variables).Returns(_variables);
|
||||||
stepContext.Setup(x => x.EnvironmentVariables).Returns(_env);
|
stepContext.Setup(x => x.EnvironmentVariables).Returns(_env);
|
||||||
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
|
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
|
||||||
|
stepContext.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
stepContext.Setup(x => x.JobContext).Returns(_jobContext);
|
stepContext.Setup(x => x.JobContext).Returns(_jobContext);
|
||||||
stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
|
stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
|
||||||
|
stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName);
|
||||||
stepContext.Setup(x => x.Complete(It.IsAny<TaskResult?>(), It.IsAny<string>(), It.IsAny<string>()))
|
stepContext.Setup(x => x.Complete(It.IsAny<TaskResult?>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((TaskResult? r, string currentOperation, string resultCode) =>
|
.Callback((TaskResult? r, string currentOperation, string resultCode) =>
|
||||||
{
|
{
|
||||||
@@ -543,6 +614,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
stepContext.Object.Result = r;
|
stepContext.Object.Result = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult().ToString());
|
||||||
|
_stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult().ToString());
|
||||||
});
|
});
|
||||||
var trace = hc.GetTrace();
|
var trace = hc.GetTrace();
|
||||||
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
|
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
new Pipelines.ContextData.DictionaryContextData()
|
new Pipelines.ContextData.DictionaryContextData()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List<MaskHint>(), resources, context, null, actions, null);
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List<MaskHint>(), resources, context, null, actions, null, null, null, null);
|
||||||
return jobRequest;
|
return jobRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
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'
|
|
||||||
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: 'docker'
|
|
||||||
image: 'Dockerfile'
|
|
||||||
args:
|
|
||||||
- 'bzz'
|
|
||||||
entrypoint: 'main.sh'
|
|
||||||
env:
|
|
||||||
Token: foo
|
|
||||||
Url: bar
|
|
||||||
pre-entrypoint: 'init.sh'
|
|
||||||
pre-if: 'success()'
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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'
|
|
||||||
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: 'docker'
|
|
||||||
image: 'Dockerfile'
|
|
||||||
args:
|
|
||||||
- 'bzz'
|
|
||||||
entrypoint: 'main.sh'
|
|
||||||
env:
|
|
||||||
Token: foo
|
|
||||||
Url: bar
|
|
||||||
pre-entrypoint: 'init.sh'
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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: 'node12'
|
|
||||||
main: 'main.js'
|
|
||||||
pre: 'init.js'
|
|
||||||
pre-if: 'cancelled()'
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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: 'node12'
|
|
||||||
main: 'main.js'
|
|
||||||
pre: 'init.js'
|
|
||||||
@@ -1 +1 @@
|
|||||||
2.165.2
|
2.168.0
|
||||||
|
|||||||
Reference in New Issue
Block a user