namespace KubernetesWorkflow.Recipe { public class ContainerRecipe { public ContainerRecipe(DateTime recipeCreatedUtc, int number, string? nameOverride, string image, ContainerResources resources, IReadOnlyDictionary nodePoolLabels, IReadOnlyList tolerations, CommandOverride commandOverride, bool isCriticalPriority, IReadOnlyList exposedPorts, IReadOnlyList internalPorts, IReadOnlyList envVars, PodLabels podLabels, PodAnnotations podAnnotations, IReadOnlyList volumes, ContainerAdditionals additionals) { RecipeCreatedUtc = recipeCreatedUtc; Number = number; NameOverride = nameOverride; Image = image; Resources = resources; NodePoolLabels = nodePoolLabels; Tolerations = tolerations; CommandOverride = commandOverride; IsCriticalPriority = isCriticalPriority; ExposedPorts = exposedPorts; InternalPorts = internalPorts; EnvVars = envVars; PodLabels = podLabels; PodAnnotations = podAnnotations; Volumes = volumes; Additionals = additionals; if (NameOverride != null) { Name = $"{K8sNameUtils.Format(NameOverride)}-{Number}"; } else { Name = $"ctnr{Number}"; } if (exposedPorts.Any(p => string.IsNullOrEmpty(p.Tag))) throw new Exception("Port tags are required for all exposed ports."); } public DateTime RecipeCreatedUtc { get; } public string Name { get; } public int Number { get; } public string? NameOverride { get; } public ContainerResources Resources { get; } public IReadOnlyDictionary NodePoolLabels { get; } public IReadOnlyList Tolerations { get; } public CommandOverride CommandOverride { get; } public bool IsCriticalPriority { get; } public string Image { get; } public IReadOnlyList ExposedPorts { get; } public IReadOnlyList InternalPorts { get; } public IReadOnlyList EnvVars { get; } public PodLabels PodLabels { get; } public PodAnnotations PodAnnotations { get; } public IReadOnlyList Volumes { get; } public ContainerAdditionals Additionals { get; } public Port? GetPortByTag(string tag) { return ExposedPorts.Concat(InternalPorts).SingleOrDefault(p => p.Tag == tag); } public override string ToString() { return $"(container-recipe: {Name}, image: {Image}, " + $"exposedPorts: {string.Join(",", ExposedPorts.Select(p => p.Number))}, " + $"internalPorts: {string.Join(",", InternalPorts.Select(p => p.Number))}, " + $"envVars: {string.Join(",", EnvVars.Select(v => v.ToString()))}, " + $"limits: {Resources}, " + $"nodePoolLabels: [{string.Join(",", NodePoolLabels.Select(kvp => $"{kvp.Key}={kvp.Value}"))}], " + $"tolerations: [{string.Join(",", Tolerations.Select(t => $"{t.Key}={t.Value}:{t.Effect}"))}], " + $"volumes: {string.Join(",", Volumes.Select(v => $"'{v.MountPath}'"))}"; } } public class Port { public Port(int number, string tag, PortProtocol protocol) { Number = number; Tag = tag; Protocol = protocol; if (string.IsNullOrWhiteSpace(Tag)) { throw new Exception("A unique port tag is required"); } } public int Number { get; } public string Tag { get; } public PortProtocol Protocol { get; } public bool IsTcp() { return Protocol == PortProtocol.TCP; } public bool IsUdp() { return Protocol == PortProtocol.UDP; } public override string ToString() { if (string.IsNullOrEmpty(Tag)) return $"untagged-port={Number}/{Protocol}"; return $"{Tag}={Number}/{Protocol}"; } } public enum PortProtocol { TCP, UDP } public record PodToleration(string Key, string Value, string Effect); public class EnvVar { public EnvVar(string name, string value) { Name = name; Value = value; } public string Name { get; } public string Value { get; } public override string ToString() { return $"'{Name}' = '{Value}'"; } } public class VolumeMount { public VolumeMount(string volumeName, string mountPath, string? subPath = null, string? resourceQuantity = null, string? secret = null, string? hostPath = null) { VolumeName = volumeName; MountPath = mountPath; SubPath = subPath; ResourceQuantity = resourceQuantity; Secret = secret; HostPath = hostPath; } public string VolumeName { get; } public string MountPath { get; } public string? SubPath { get; } public string? ResourceQuantity { get; } public string? Secret { get; } public string? HostPath { get; } } }