using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Services.WebApi; using Newtonsoft.Json; namespace GitHub.DistributedTask.Pipelines { [DataContract] [EditorBrowsable(EditorBrowsableState.Never)] public sealed class AgentJobRequestMessage { [JsonConstructor] internal AgentJobRequestMessage() { } /// /// Job request message sent to the runner /// /// Hierarchy of environment variables to overlay, last wins. public AgentJobRequestMessage( TaskOrchestrationPlanReference plan, TimelineReference timeline, Guid jobId, String jobDisplayName, String jobName, TemplateToken jobContainer, TemplateToken jobServiceContainers, IList environmentVariables, IDictionary variables, IList maskHints, JobResources jobResources, DictionaryContextData contextData, WorkspaceOptions workspaceOptions, IEnumerable steps, IEnumerable scopes, IList fileTable, TemplateToken jobOutputs, IList defaults) { this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest; this.Plan = plan; this.JobId = jobId; this.JobDisplayName = jobDisplayName; this.JobName = jobName; this.JobContainer = jobContainer; this.JobServiceContainers = jobServiceContainers; this.Timeline = timeline; this.Resources = jobResources; this.Workspace = workspaceOptions; this.JobOutputs = jobOutputs; m_variables = new Dictionary(variables, StringComparer.OrdinalIgnoreCase); m_maskHints = new List(maskHints); m_steps = new List(steps); if (scopes != null) { m_scopes = new List(scopes); } if (environmentVariables?.Count > 0) { m_environmentVariables = new List(environmentVariables); } if (defaults?.Count > 0) { m_defaults = new List(defaults); } this.ContextData = new Dictionary(StringComparer.OrdinalIgnoreCase); if (contextData?.Count > 0) { foreach (var pair in contextData) { this.ContextData[pair.Key] = pair.Value; } } if (fileTable?.Count > 0) { m_fileTable = new List(fileTable); } } [DataMember] public String MessageType { get; private set; } [DataMember] public TaskOrchestrationPlanReference Plan { get; private set; } [DataMember] public TimelineReference Timeline { get; private set; } [DataMember] public Guid JobId { get; private set; } [DataMember] public String JobDisplayName { get; private set; } [DataMember] public String JobName { get; private set; } [DataMember(EmitDefaultValue = false)] public TemplateToken JobContainer { get; private set; } [DataMember(EmitDefaultValue = false)] public TemplateToken JobServiceContainers { get; private set; } [DataMember(EmitDefaultValue = false)] public TemplateToken JobOutputs { get; private set; } [DataMember] public Int64 RequestId { get; internal set; } [DataMember] public DateTime LockedUntil { get; internal set; } [DataMember] public JobResources Resources { get; private set; } [DataMember(EmitDefaultValue = false)] [EditorBrowsable(EditorBrowsableState.Never)] public IDictionary ContextData { get; private set; } [DataMember(EmitDefaultValue = false)] public WorkspaceOptions Workspace { get; private set; } /// /// Gets the collection of mask hints /// public List MaskHints { get { if (m_maskHints == null) { m_maskHints = new List(); } return m_maskHints; } } /// /// Gets the hierarchy of environment variables to overlay, last wins. /// public IList EnvironmentVariables { get { if (m_environmentVariables == null) { m_environmentVariables = new List(); } return m_environmentVariables; } } /// /// Gets the hierarchy of defaults to overlay, last wins. /// public IList Defaults { get { if (m_defaults == null) { m_defaults = new List(); } return m_defaults; } } /// /// Gets the collection of variables associated with the current context. /// public IDictionary Variables { get { if (m_variables == null) { m_variables = new Dictionary(StringComparer.OrdinalIgnoreCase); } return m_variables; } } public IList Steps { get { if (m_steps == null) { m_steps = new List(); } return m_steps; } } public IList Scopes { get { if (m_scopes == null) { m_scopes = new List(); } return m_scopes; } } /// /// Gets the table of files used when parsing the pipeline (e.g. yaml files) /// public IList FileTable { get { if (m_fileTable == null) { m_fileTable = new List(); } return m_fileTable; } } // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere public void SetJobSidecarContainers(IDictionary value) { m_jobSidecarContainers = value; } public TaskAgentMessage GetAgentMessage() { var body = JsonUtility.ToString(this); return new TaskAgentMessage { Body = body, MessageType = JobRequestMessageTypes.PipelineAgentJobRequest }; } // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere internal static TemplateToken ConvertToTemplateToken(ContainerResource resource) { var result = new MappingToken(null, null, null); var image = resource.Image; if (!string.IsNullOrEmpty(image)) { result.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, image)); } var options = resource.Options; if (!string.IsNullOrEmpty(options)) { result.Add(new StringToken(null, null, null, "options"), new StringToken(null, null, null, options)); } var environment = resource.Environment; if (environment?.Count > 0) { var mapping = new MappingToken(null, null, null); foreach (var pair in environment) { mapping.Add(new StringToken(null, null, null, pair.Key), new StringToken(null, null, null, pair.Value)); } result.Add(new StringToken(null, null, null, "env"), mapping); } var ports = resource.Ports; if (ports?.Count > 0) { var sequence = new SequenceToken(null, null, null); foreach (var item in ports) { sequence.Add(new StringToken(null, null, null, item)); } result.Add(new StringToken(null, null, null, "ports"), sequence); } var volumes = resource.Volumes; if (volumes?.Count > 0) { var sequence = new SequenceToken(null, null, null); foreach (var item in volumes) { sequence.Add(new StringToken(null, null, null, item)); } result.Add(new StringToken(null, null, null, "volumes"), sequence); } return result; } [OnDeserialized] private void OnDeserialized(StreamingContext context) { // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere if (JobContainer is StringToken jobContainerStringToken) { var resourceAlias = jobContainerStringToken.Value; var resource = Resources?.Containers.SingleOrDefault(x => string.Equals(x.Alias, resourceAlias, StringComparison.OrdinalIgnoreCase)); if (resource != null) { JobContainer = ConvertToTemplateToken(resource); m_jobContainerResourceAlias = resourceAlias; } } // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere if (m_jobSidecarContainers?.Count > 0 && (JobServiceContainers == null || JobServiceContainers.Type == TokenType.Null)) { var services = new MappingToken(null, null, null); foreach (var pair in m_jobSidecarContainers) { var networkAlias = pair.Key; var serviceResourceAlias = pair.Value; var serviceResource = Resources.Containers.Single(x => string.Equals(x.Alias, serviceResourceAlias, StringComparison.OrdinalIgnoreCase)); services.Add(new StringToken(null, null, null, networkAlias), ConvertToTemplateToken(serviceResource)); } JobServiceContainers = services; } } [OnSerializing] private void OnSerializing(StreamingContext context) { if (m_environmentVariables?.Count == 0) { m_environmentVariables = null; } if (m_defaults?.Count == 0) { m_defaults = null; } if (m_fileTable?.Count == 0) { m_fileTable = null; } if (m_maskHints?.Count == 0) { m_maskHints = null; } else if (m_maskHints != null) { m_maskHints = new List(this.m_maskHints.Distinct()); } if (m_scopes?.Count == 0) { m_scopes = null; } if (m_variables?.Count == 0) { m_variables = null; } // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere if (!string.IsNullOrEmpty(m_jobContainerResourceAlias)) { JobContainer = new StringToken(null, null, null, m_jobContainerResourceAlias); } } [DataMember(Name = "EnvironmentVariables", EmitDefaultValue = false)] private List m_environmentVariables; [DataMember(Name = "Defaults", EmitDefaultValue = false)] private List m_defaults; [DataMember(Name = "FileTable", EmitDefaultValue = false)] private List m_fileTable; [DataMember(Name = "Mask", EmitDefaultValue = false)] private List m_maskHints; [DataMember(Name = "Steps", EmitDefaultValue = false)] private List m_steps; [DataMember(Name = "Scopes", EmitDefaultValue = false)] private List m_scopes; [DataMember(Name = "Variables", EmitDefaultValue = false)] private IDictionary m_variables; // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere [DataMember(Name = "JobSidecarContainers", EmitDefaultValue = false)] private IDictionary m_jobSidecarContainers; // todo: remove after feature-flag DistributedTask.EvaluateContainerOnRunner is enabled everywhere [IgnoreDataMember] private string m_jobContainerResourceAlias; } }