mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
481 lines
22 KiB
C#
481 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|
|
|
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|
{
|
|
/// <summary>
|
|
/// This models the root schema object and contains definitions
|
|
/// </summary>
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public sealed class TemplateSchema
|
|
{
|
|
internal TemplateSchema()
|
|
: this(null)
|
|
{
|
|
}
|
|
|
|
private TemplateSchema(MappingToken mapping)
|
|
{
|
|
// Add built-in type: null
|
|
var nullDefinition = new NullDefinition();
|
|
Definitions.Add(TemplateConstants.Null, nullDefinition);
|
|
|
|
// Add built-in type: boolean
|
|
var booleanDefinition = new BooleanDefinition();
|
|
Definitions.Add(TemplateConstants.Boolean, booleanDefinition);
|
|
|
|
// Add built-in type: number
|
|
var numberDefinition = new NumberDefinition();
|
|
Definitions.Add(TemplateConstants.Number, numberDefinition);
|
|
|
|
// Add built-in type: string
|
|
var stringDefinition = new StringDefinition();
|
|
Definitions.Add(TemplateConstants.String, stringDefinition);
|
|
|
|
// Add built-in type: sequence
|
|
var sequenceDefinition = new SequenceDefinition { ItemType = TemplateConstants.Any };
|
|
Definitions.Add(TemplateConstants.Sequence, sequenceDefinition);
|
|
|
|
// Add built-in type: mapping
|
|
var mappingDefinition = new MappingDefinition { LooseKeyType = TemplateConstants.String, LooseValueType = TemplateConstants.Any };
|
|
Definitions.Add(TemplateConstants.Mapping, mappingDefinition);
|
|
|
|
// Add built-in type: any
|
|
var anyDefinition = new OneOfDefinition();
|
|
anyDefinition.OneOf.Add(TemplateConstants.Null);
|
|
anyDefinition.OneOf.Add(TemplateConstants.Boolean);
|
|
anyDefinition.OneOf.Add(TemplateConstants.Number);
|
|
anyDefinition.OneOf.Add(TemplateConstants.String);
|
|
anyDefinition.OneOf.Add(TemplateConstants.Sequence);
|
|
anyDefinition.OneOf.Add(TemplateConstants.Mapping);
|
|
Definitions.Add(TemplateConstants.Any, anyDefinition);
|
|
|
|
if (mapping != null)
|
|
{
|
|
foreach (var pair in mapping)
|
|
{
|
|
var key = pair.Key.AssertString($"{TemplateConstants.TemplateSchema} key");
|
|
switch (key.Value)
|
|
{
|
|
case TemplateConstants.Version:
|
|
var version = pair.Value.AssertString(TemplateConstants.Version);
|
|
Version = version.Value;
|
|
break;
|
|
|
|
case TemplateConstants.Definitions:
|
|
var definitions = pair.Value.AssertMapping(TemplateConstants.Definitions);
|
|
foreach (var definitionsPair in definitions)
|
|
{
|
|
var definitionsKey = definitionsPair.Key.AssertString($"{TemplateConstants.Definitions} key");
|
|
var definitionsValue = definitionsPair.Value.AssertMapping(TemplateConstants.Definition);
|
|
var definition = default(Definition);
|
|
foreach (var definitionPair in definitionsValue)
|
|
{
|
|
var definitionKey = definitionPair.Key.AssertString($"{TemplateConstants.Definition} key");
|
|
switch (definitionKey.Value)
|
|
{
|
|
case TemplateConstants.Null:
|
|
definition = new NullDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.Boolean:
|
|
definition = new BooleanDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.Number:
|
|
definition = new NumberDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.String:
|
|
definition = new StringDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.Sequence:
|
|
definition = new SequenceDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.Mapping:
|
|
definition = new MappingDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.OneOf:
|
|
definition = new OneOfDefinition(definitionsValue);
|
|
break;
|
|
|
|
case TemplateConstants.Context:
|
|
case TemplateConstants.Description:
|
|
continue;
|
|
|
|
default:
|
|
definitionKey.AssertUnexpectedValue("definition mapping key"); // throws
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (definition == null)
|
|
{
|
|
throw new ArgumentException($"Unable to determine definition details. Specify the '{TemplateConstants.Structure}' property");
|
|
}
|
|
|
|
Definitions.Add(definitionsKey.Value, definition);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
key.AssertUnexpectedValue($"{TemplateConstants.TemplateSchema} key"); // throws
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal Dictionary<String, Definition> Definitions { get; } = new Dictionary<String, Definition>(StringComparer.Ordinal);
|
|
|
|
internal String Version { get; }
|
|
|
|
/// <summary>
|
|
/// Loads a user's schema file
|
|
/// </summary>
|
|
internal static TemplateSchema Load(IObjectReader objectReader)
|
|
{
|
|
var context = new TemplateContext
|
|
{
|
|
CancellationToken = CancellationToken.None,
|
|
Errors = new TemplateValidationErrors(maxErrors: 10, maxMessageLength: 500),
|
|
Memory = new TemplateMemory(
|
|
maxDepth: 50,
|
|
maxEvents: 1000000, // 1 million
|
|
maxBytes: 1024 * 1024), // 1 mb
|
|
TraceWriter = new EmptyTraceWriter(),
|
|
};
|
|
|
|
var value = TemplateReader.Read(context, TemplateConstants.TemplateSchema, objectReader, null, Schema, out _);
|
|
|
|
if (context.Errors.Count > 0)
|
|
{
|
|
throw new TemplateValidationException(context.Errors);
|
|
}
|
|
|
|
var mapping = value.AssertMapping(TemplateConstants.TemplateSchema);
|
|
var schema = new TemplateSchema(mapping);
|
|
schema.Validate();
|
|
return schema;
|
|
}
|
|
|
|
internal IEnumerable<T> Get<T>(Definition definition)
|
|
where T : Definition
|
|
{
|
|
if (definition is T match)
|
|
{
|
|
yield return match;
|
|
}
|
|
else if (definition is OneOfDefinition oneOf)
|
|
{
|
|
foreach (var reference in oneOf.OneOf)
|
|
{
|
|
var nestedDefinition = GetDefinition(reference);
|
|
if (nestedDefinition is T match2)
|
|
{
|
|
yield return match2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal Definition GetDefinition(String type)
|
|
{
|
|
if (Definitions.TryGetValue(type, out Definition value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
throw new ArgumentException($"Schema definition '{type}' not found");
|
|
}
|
|
|
|
internal Boolean HasProperties(MappingDefinition definition)
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
if (definition.Properties.Count > 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(definition.Inherits))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
definition = GetDefinition(definition.Inherits) as MappingDefinition;
|
|
}
|
|
|
|
throw new InvalidOperationException("Inheritance depth exceeded 10");
|
|
}
|
|
|
|
internal Boolean TryGetProperty(
|
|
MappingDefinition definition,
|
|
String name,
|
|
out String type)
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
if (definition.Properties.TryGetValue(name, out PropertyValue property))
|
|
{
|
|
type = property.Type;
|
|
return true;
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(definition.Inherits))
|
|
{
|
|
type = default;
|
|
return false;
|
|
}
|
|
|
|
definition = GetDefinition(definition.Inherits) as MappingDefinition;
|
|
}
|
|
|
|
throw new InvalidOperationException("Inheritance depth exceeded 10");
|
|
}
|
|
|
|
internal Boolean TryMatchKey(
|
|
List<MappingDefinition> definitions,
|
|
String key,
|
|
out String valueType)
|
|
{
|
|
valueType = null;
|
|
|
|
// Check for a matching well known property
|
|
var notFoundInSome = false;
|
|
for (var i = 0; i < definitions.Count; i++)
|
|
{
|
|
var definition = definitions[i];
|
|
|
|
if (TryGetProperty(definition, key, out String t))
|
|
{
|
|
if (valueType == null)
|
|
{
|
|
valueType = t;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
notFoundInSome = true;
|
|
}
|
|
}
|
|
|
|
// Check if found
|
|
if (valueType != null)
|
|
{
|
|
// Filter the matched definitions if needed
|
|
if (notFoundInSome)
|
|
{
|
|
for (var i = 0; i < definitions.Count;)
|
|
{
|
|
if (TryGetProperty(definitions[i], key, out _))
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
definitions.RemoveAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The built-in schema for reading schema files
|
|
/// </summary>
|
|
private static TemplateSchema Schema
|
|
{
|
|
get
|
|
{
|
|
if (s_schema == null)
|
|
{
|
|
var schema = new TemplateSchema();
|
|
|
|
StringDefinition stringDefinition;
|
|
SequenceDefinition sequenceDefinition;
|
|
MappingDefinition mappingDefinition;
|
|
OneOfDefinition oneOfDefinition;
|
|
|
|
// template-schema
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(TemplateConstants.NonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(TemplateConstants.Definitions));
|
|
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
|
|
|
|
// definitions
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
|
mappingDefinition.LooseValueType = TemplateConstants.Definition;
|
|
schema.Definitions.Add(TemplateConstants.Definitions, mappingDefinition);
|
|
|
|
// definition
|
|
oneOfDefinition = new OneOfDefinition();
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.NullDefinition);
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.BooleanDefinition);
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.NumberDefinition);
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.StringDefinition);
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.SequenceDefinition);
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.MappingDefinition);
|
|
oneOfDefinition.OneOf.Add(TemplateConstants.OneOfDefinition);
|
|
schema.Definitions.Add(TemplateConstants.Definition, oneOfDefinition);
|
|
|
|
// null-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(TemplateConstants.NullDefinitionProperties));
|
|
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
|
|
|
|
// null-definition-properties
|
|
mappingDefinition = new MappingDefinition();
|
|
schema.Definitions.Add(TemplateConstants.NullDefinitionProperties, mappingDefinition);
|
|
|
|
// boolean-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(TemplateConstants.BooleanDefinitionProperties));
|
|
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
|
|
|
|
// boolean-definition-properties
|
|
mappingDefinition = new MappingDefinition();
|
|
schema.Definitions.Add(TemplateConstants.BooleanDefinitionProperties, mappingDefinition);
|
|
|
|
// number-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(TemplateConstants.NumberDefinitionProperties));
|
|
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
|
|
|
|
// number-definition-properties
|
|
mappingDefinition = new MappingDefinition();
|
|
schema.Definitions.Add(TemplateConstants.NumberDefinitionProperties, mappingDefinition);
|
|
|
|
// string-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(TemplateConstants.StringDefinitionProperties));
|
|
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
|
|
|
|
// string-definition-properties
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(TemplateConstants.NonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(TemplateConstants.Boolean));
|
|
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(TemplateConstants.Boolean));
|
|
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
|
|
|
// sequence-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(TemplateConstants.SequenceDefinitionProperties));
|
|
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
|
|
|
|
// sequence-definition-properties
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(TemplateConstants.NonEmptyString));
|
|
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
|
|
|
// mapping-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(TemplateConstants.MappingDefinitionProperties));
|
|
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
|
|
|
|
// mapping-definition-properties
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(TemplateConstants.Properties));
|
|
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(TemplateConstants.NonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(TemplateConstants.NonEmptyString));
|
|
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
|
|
|
|
// properties
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
|
mappingDefinition.LooseValueType = TemplateConstants.NonEmptyString;
|
|
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
|
|
|
|
// one-of-definition
|
|
mappingDefinition = new MappingDefinition();
|
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
|
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
|
|
|
|
// non-empty-string
|
|
stringDefinition = new StringDefinition();
|
|
stringDefinition.RequireNonEmpty = true;
|
|
schema.Definitions.Add(TemplateConstants.NonEmptyString, stringDefinition);
|
|
|
|
// sequence-of-non-empty-string
|
|
sequenceDefinition = new SequenceDefinition();
|
|
sequenceDefinition.ItemType = TemplateConstants.NonEmptyString;
|
|
schema.Definitions.Add(TemplateConstants.SequenceOfNonEmptyString, sequenceDefinition);
|
|
|
|
schema.Validate();
|
|
|
|
Interlocked.CompareExchange(ref s_schema, schema, null);
|
|
}
|
|
|
|
return s_schema;
|
|
}
|
|
}
|
|
|
|
private void Validate()
|
|
{
|
|
var oneOfPairs = new List<KeyValuePair<String, OneOfDefinition>>();
|
|
|
|
foreach (var pair in Definitions)
|
|
{
|
|
var name = pair.Key;
|
|
|
|
if (!s_definitionNameRegex.IsMatch(name ?? String.Empty))
|
|
{
|
|
throw new ArgumentException($"Invalid definition name '{name}'");
|
|
}
|
|
|
|
var definition = pair.Value;
|
|
|
|
// Delay validation for 'one-of' definitions
|
|
if (definition is OneOfDefinition oneOf)
|
|
{
|
|
oneOfPairs.Add(new KeyValuePair<String, OneOfDefinition>(name, oneOf));
|
|
}
|
|
// Otherwise validate now
|
|
else
|
|
{
|
|
definition.Validate(this, name);
|
|
}
|
|
}
|
|
|
|
// Validate 'one-of' definitions
|
|
foreach (var pair in oneOfPairs)
|
|
{
|
|
var name = pair.Key;
|
|
var oneOf = pair.Value;
|
|
oneOf.Validate(this, name);
|
|
}
|
|
}
|
|
|
|
private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled);
|
|
private static TemplateSchema s_schema;
|
|
}
|
|
}
|