UNPKG

cdk8s-plus-25

Version:

cdk8s+ is a software development framework that provides high level abstractions for authoring Kubernetes applications. cdk8s-plus-25 synthesizes Kubernetes manifests for Kubernetes 1.25.0

1,129 lines 148 kB
"use strict"; var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r; Object.defineProperty(exports, "__esModule", { value: true }); exports.PodConnections = exports.PodConnectionsIsolation = exports.PodScheduling = exports.Topology = exports.Node = exports.NamedNode = exports.TaintedNode = exports.LabeledNode = exports.Pods = exports.NodeTaintQuery = exports.TaintEffect = exports.LabelExpression = exports.NodeLabelQuery = exports.DnsPolicy = exports.FsGroupChangePolicy = exports.RestartPolicy = exports.PodSecurityContext = exports.PodDns = exports.Pod = exports.LabelSelector = exports.AbstractPod = void 0; const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti"); const cdk8s_1 = require("cdk8s"); const constructs_1 = require("constructs"); const base = require("./base"); const container = require("./container"); const k8s = require("./imports/k8s"); const networkpolicy = require("./network-policy"); const utils_1 = require("./utils"); class AbstractPod extends base.Resource { constructor(scope, id, props = {}) { super(scope, id); this._containers = []; this._initContainers = []; this._hostAliases = []; this._volumes = new Map(); this.restartPolicy = props.restartPolicy ?? RestartPolicy.ALWAYS; this.serviceAccount = props.serviceAccount; this.securityContext = new PodSecurityContext(props.securityContext); this.dns = new PodDns(props.dns); this.dockerRegistryAuth = props.dockerRegistryAuth; this.automountServiceAccountToken = props.automountServiceAccountToken ?? false; this.isolate = props.isolate ?? false; this.hostNetwork = props.hostNetwork ?? false; this.terminationGracePeriod = props.terminationGracePeriod ?? cdk8s_1.Duration.seconds(30); if (props.containers) { props.containers.forEach(c => this.addContainer(c)); } if (props.volumes) { props.volumes.forEach(v => this.addVolume(v)); } if (props.initContainers) { props.initContainers.forEach(c => this.addInitContainer(c)); } if (props.hostAliases) { props.hostAliases.forEach(c => this.addHostAlias(c)); } } get containers() { return [...this._containers]; } get initContainers() { return [...this._initContainers]; } get volumes() { return Array.from(this._volumes.values()); } get hostAliases() { return [...this._hostAliases]; } /** * @see IPodSelector.toPodSelectorConfig() */ toPodSelectorConfig() { const podAddress = this.podMetadata.getLabel(Pod.ADDRESS_LABEL); if (!podAddress) { // shouldn't happen because we add this label automatically in both pods and workloads. throw new Error(`Unable to create a label selector since ${Pod.ADDRESS_LABEL} label is missing`); } return { labelSelector: LabelSelector.of({ labels: { [Pod.ADDRESS_LABEL]: podAddress } }), namespaces: this.metadata.namespace ? { names: [this.metadata.namespace], } : undefined, }; } /** * @see INetworkPolicyPeer.toNetworkPolicyPeerConfig() */ toNetworkPolicyPeerConfig() { return { podSelector: this.toPodSelectorConfig() }; } /** * @see INetworkPolicyPeer.toPodSelector() */ toPodSelector() { return this; } addContainer(cont) { const impl = new container.Container(cont); this.attachContainer(impl); return impl; } attachContainer(cont) { this._containers.push(cont); } addInitContainer(cont) { // https://kubernetes.io/docs/concepts/workloads/pods/init-containers/#differences-from-regular-containers if (cont.readiness) { throw new Error('Init containers must not have a readiness probe'); } if (cont.liveness) { throw new Error('Init containers must not have a liveness probe'); } if (cont.startup) { throw new Error('Init containers must not have a startup probe'); } const impl = new container.Container({ ...cont, name: cont.name ?? `init-${this._initContainers.length}`, }); this._initContainers.push(impl); return impl; } addHostAlias(hostAlias) { this._hostAliases.push(hostAlias); } addVolume(vol) { const existingVolume = this._volumes.get(vol.name); if (existingVolume) { throw new Error(`Volume with name ${vol.name} already exists`); } this._volumes.set(vol.name, vol); } /** * @see ISubect.toSubjectConfiguration() */ toSubjectConfiguration() { if (!this.serviceAccount && !this.automountServiceAccountToken) { throw new Error(`${this.name} cannot be converted to a role binding subject:` + ' You must either assign a service account to it, or use \'automountServiceAccountToken: true\''); } // 'default' is assumed to be the name of the default service account // in the cluster. const serviceAccountName = this.serviceAccount?.name ?? 'default'; return { kind: 'ServiceAccount', name: serviceAccountName, apiGroup: '', }; } /** * @internal */ _toPodSpec() { if (this.containers.length === 0) { throw new Error('PodSpec must have at least 1 container'); } const volumes = new Map(); const containers = []; const initContainers = []; for (const cont of this.containers) { // automatically add volume from the container mount // to this pod so thats its available to the container. for (const mount of cont.mounts) { addVolume(mount.volume); } containers.push(cont._toKube()); } for (const cont of this.initContainers) { // automatically add volume from the container mount // to this pod so thats its available to the container. for (const mount of cont.mounts) { addVolume(mount.volume); } initContainers.push(cont._toKube()); } for (const vol of this.volumes) { addVolume(vol); } function addVolume(vol) { const existingVolume = volumes.get(vol.name); // its ok to call this function twice on the same volume, but its not ok to // call it twice on a different volume with the same name. if (existingVolume && existingVolume !== vol) { throw new Error(`Invalid mount configuration. At least two different volumes have the same name: ${vol.name}`); } volumes.set(vol.name, vol); } const dns = this.dns._toKube(); return { restartPolicy: this.restartPolicy, serviceAccountName: this.serviceAccount?.name, containers: containers, securityContext: utils_1.undefinedIfEmpty(this.securityContext._toKube()), initContainers: utils_1.undefinedIfEmpty(initContainers), hostAliases: utils_1.undefinedIfEmpty(this.hostAliases), volumes: utils_1.undefinedIfEmpty(Array.from(volumes.values()).map(v => v._toKube())), dnsPolicy: dns.policy, dnsConfig: utils_1.undefinedIfEmpty(dns.config), hostname: dns.hostname, subdomain: dns.subdomain, setHostnameAsFqdn: dns.hostnameAsFQDN, imagePullSecrets: this.dockerRegistryAuth ? [{ name: this.dockerRegistryAuth.name }] : undefined, automountServiceAccountToken: this.automountServiceAccountToken, hostNetwork: this.hostNetwork, terminationGracePeriodSeconds: this.terminationGracePeriod?.toSeconds(), }; } } exports.AbstractPod = AbstractPod; _a = JSII_RTTI_SYMBOL_1; AbstractPod[_a] = { fqn: "cdk8s-plus-25.AbstractPod", version: "2.22.79" }; /** * Match a resource by labels. */ class LabelSelector { constructor(expressions, labels) { this.expressions = expressions; this.labels = labels; } static of(options = {}) { return new LabelSelector(options.expressions ?? [], options.labels ?? {}); } isEmpty() { return this.expressions.length === 0 && Object.keys(this.labels).length === 0; } /** * @internal */ _toKube() { if (this.isEmpty()) { return {}; } return { matchExpressions: utils_1.undefinedIfEmpty(this.expressions.map(q => ({ key: q.key, operator: q.operator, values: q.values }))), matchLabels: utils_1.undefinedIfEmpty(this.labels), }; } } exports.LabelSelector = LabelSelector; _b = JSII_RTTI_SYMBOL_1; LabelSelector[_b] = { fqn: "cdk8s-plus-25.LabelSelector", version: "2.22.79" }; /** * Pod is a collection of containers that can run on a host. This resource is * created by clients and scheduled onto hosts. */ class Pod extends AbstractPod { constructor(scope, id, props = {}) { super(scope, id, props); this.resourceType = 'pods'; this.apiObject = new k8s.KubePod(this, 'Resource', { metadata: props.metadata, spec: cdk8s_1.Lazy.any({ produce: () => this._toKube() }), }); this.metadata.addLabel(Pod.ADDRESS_LABEL, cdk8s_1.Names.toLabelValue(this)); this.scheduling = new PodScheduling(this); this.connections = new PodConnections(this); if (this.isolate) { this.connections.isolate(); } } get podMetadata() { return this.metadata; } /** * @internal */ _toKube() { const scheduling = this.scheduling._toKube(); return { ...this._toPodSpec(), affinity: scheduling.affinity, nodeName: scheduling.nodeName, tolerations: scheduling.tolerations, }; } } exports.Pod = Pod; _c = JSII_RTTI_SYMBOL_1; Pod[_c] = { fqn: "cdk8s-plus-25.Pod", version: "2.22.79" }; /** * This label is autoamtically added by cdk8s to any pod. It provides * a unique and stable identifier for the pod. */ Pod.ADDRESS_LABEL = 'cdk8s.io/metadata.addr'; /** * Holds dns settings of the pod. */ class PodDns { constructor(props = {}) { this.hostname = props.hostname; this.subdomain = props.subdomain; this.policy = props.policy ?? DnsPolicy.CLUSTER_FIRST; this.hostnameAsFQDN = props.hostnameAsFQDN ?? false; this._nameservers = props.nameservers ?? []; this._searches = props.searches ?? []; this._options = props.options ?? []; } /** * Nameservers defined for this pod. */ get nameservers() { return [...this._nameservers]; } /** * Search domains defined for this pod. */ get searches() { return [...this._searches]; } /** * Custom dns options defined for this pod. */ get options() { return [...this._options]; } /** * Add a nameserver. */ addNameserver(...nameservers) { this._nameservers.push(...nameservers); } /** * Add a search domain. */ addSearch(...searches) { this._searches.push(...searches); } /** * Add a custom option. */ addOption(...options) { this._options.push(...options); } /** * @internal */ _toKube() { if (this.policy === DnsPolicy.NONE && this.nameservers.length === 0) { throw new Error('When dns policy is set to NONE, at least one nameserver is required'); } if (this.nameservers.length > 3) { throw new Error('There can be at most 3 nameservers specified'); } if (this.searches.length > 6) { throw new Error('There can be at most 6 search domains specified'); } return { hostname: this.hostname, subdomain: this.subdomain, hostnameAsFQDN: this.hostnameAsFQDN, policy: this.policy, config: { nameservers: utils_1.undefinedIfEmpty(this.nameservers), searches: utils_1.undefinedIfEmpty(this.searches), options: utils_1.undefinedIfEmpty(this.options), }, }; } } exports.PodDns = PodDns; _d = JSII_RTTI_SYMBOL_1; PodDns[_d] = { fqn: "cdk8s-plus-25.PodDns", version: "2.22.79" }; /** * Holds pod-level security attributes and common container settings. */ class PodSecurityContext { constructor(props = {}) { this._sysctls = []; this.ensureNonRoot = props.ensureNonRoot ?? true; this.fsGroupChangePolicy = props.fsGroupChangePolicy ?? FsGroupChangePolicy.ALWAYS; this.user = props.user; this.group = props.group; this.fsGroup = props.fsGroup; for (const sysctl of props.sysctls ?? []) { this._sysctls.push(sysctl); } } get sysctls() { return [...this._sysctls]; } /** * @internal */ _toKube() { return { runAsGroup: this.group, runAsUser: this.user, fsGroup: this.fsGroup, runAsNonRoot: this.ensureNonRoot, fsGroupChangePolicy: this.fsGroupChangePolicy, sysctls: utils_1.undefinedIfEmpty(this._sysctls), }; } } exports.PodSecurityContext = PodSecurityContext; _e = JSII_RTTI_SYMBOL_1; PodSecurityContext[_e] = { fqn: "cdk8s-plus-25.PodSecurityContext", version: "2.22.79" }; /** * Restart policy for all containers within the pod. */ var RestartPolicy; (function (RestartPolicy) { /** * Always restart the pod after it exits. */ RestartPolicy["ALWAYS"] = "Always"; /** * Only restart if the pod exits with a non-zero exit code. */ RestartPolicy["ON_FAILURE"] = "OnFailure"; /** * Never restart the pod. */ RestartPolicy["NEVER"] = "Never"; })(RestartPolicy = exports.RestartPolicy || (exports.RestartPolicy = {})); var FsGroupChangePolicy; (function (FsGroupChangePolicy) { /** * Only change permissions and ownership if permission and ownership of root directory does * not match with expected permissions of the volume. * This could help shorten the time it takes to change ownership and permission of a volume */ FsGroupChangePolicy["ON_ROOT_MISMATCH"] = "OnRootMismatch"; /** * Always change permission and ownership of the volume when volume is mounted. */ FsGroupChangePolicy["ALWAYS"] = "Always"; })(FsGroupChangePolicy = exports.FsGroupChangePolicy || (exports.FsGroupChangePolicy = {})); /** * Pod DNS policies. */ var DnsPolicy; (function (DnsPolicy) { /** * Any DNS query that does not match the configured cluster domain suffix, * such as "www.kubernetes.io", is forwarded to the * upstream nameserver inherited from the node. * Cluster administrators may have extra stub-domain and upstream DNS servers configured. */ DnsPolicy["CLUSTER_FIRST"] = "ClusterFirst"; /** * For Pods running with hostNetwork, you should * explicitly set its DNS policy "ClusterFirstWithHostNet". */ DnsPolicy["CLUSTER_FIRST_WITH_HOST_NET"] = "ClusterFirstWithHostNet"; /** * The Pod inherits the name resolution configuration * from the node that the pods run on. */ DnsPolicy["DEFAULT"] = "Default"; /** * It allows a Pod to ignore DNS settings from the Kubernetes environment. * All DNS settings are supposed to be provided using the dnsConfig * field in the Pod Spec. */ DnsPolicy["NONE"] = "None"; })(DnsPolicy = exports.DnsPolicy || (exports.DnsPolicy = {})); /** * Represents a query that can be performed against nodes with labels. */ class NodeLabelQuery { constructor(key, operator, values) { this.key = key; this.operator = operator; this.values = values; } /** * Requires value of label `key` to equal `value`. */ static is(key, value) { return NodeLabelQuery.in(key, [value]); } /** * Requires value of label `key` to be one of `values`. */ static in(key, values) { return new NodeLabelQuery(key, 'In', values); } /** * Requires value of label `key` to be none of `values`. */ static notIn(key, values) { return new NodeLabelQuery(key, 'NotIn', values); } /** * Requires label `key` to exist. */ static exists(key) { return new NodeLabelQuery(key, 'Exists', undefined); } /** * Requires label `key` to not exist. */ static doesNotExist(key) { return new NodeLabelQuery(key, 'DoesNotExist', undefined); } /** * Requires value of label `key` to greater than all elements in `values`. */ static gt(key, values) { return new NodeLabelQuery(key, 'Gt', values); } /** * Requires value of label `key` to less than all elements in `values`. */ static lt(key, values) { return new NodeLabelQuery(key, 'Lt', values); } /** * @internal */ _toKube() { return { key: this.key, operator: this.operator, values: this.values, }; } } exports.NodeLabelQuery = NodeLabelQuery; _f = JSII_RTTI_SYMBOL_1; NodeLabelQuery[_f] = { fqn: "cdk8s-plus-25.NodeLabelQuery", version: "2.22.79" }; /** * Represents a query that can be performed against resources with labels. */ class LabelExpression { constructor(key, operator, values) { this.key = key; this.operator = operator; this.values = values; } /** * Requires value of label `key` to be one of `values`. */ static in(key, values) { return new LabelExpression(key, 'In', values); } /** * Requires value of label `key` to be none of `values`. */ static notIn(key, values) { return new LabelExpression(key, 'NotIn', values); } /** * Requires label `key` to exist. */ static exists(key) { return new LabelExpression(key, 'Exists', undefined); } /** * Requires label `key` to not exist. */ static doesNotExist(key) { return new LabelExpression(key, 'DoesNotExist', undefined); } } exports.LabelExpression = LabelExpression; _g = JSII_RTTI_SYMBOL_1; LabelExpression[_g] = { fqn: "cdk8s-plus-25.LabelExpression", version: "2.22.79" }; /** * Taint effects. */ var TaintEffect; (function (TaintEffect) { /** * This means that no pod will be able to schedule * onto the node unless it has a matching toleration. */ TaintEffect["NO_SCHEDULE"] = "NoSchedule"; /** * This is a "preference" or "soft" version of `NO_SCHEDULE` -- the system * will try to avoid placing a pod that does not tolerate the taint on the node, * but it is not required */ TaintEffect["PREFER_NO_SCHEDULE"] = "PreferNoSchedule"; /** * This affects pods that are already running on the node as follows: * * - Pods that do not tolerate the taint are evicted immediately. * - Pods that tolerate the taint without specifying `duration` remain bound forever. * - Pods that tolerate the taint with a specified `duration` remain bound for * the specified amount of time. */ TaintEffect["NO_EXECUTE"] = "NoExecute"; })(TaintEffect = exports.TaintEffect || (exports.TaintEffect = {})); /** * Taint queries that can be perfomed against nodes. */ class NodeTaintQuery { constructor(operator, key, value, effect, evictAfter) { this.operator = operator; this.key = key; this.value = value; this.effect = effect; this.evictAfter = evictAfter; if (evictAfter && effect !== TaintEffect.NO_EXECUTE) { throw new Error('Only \'NO_EXECUTE\' effects can specify \'evictAfter\''); } } /** * Matches a taint with a specific key and value. */ static is(key, value, options = {}) { return new NodeTaintQuery('Equal', key, value, options.effect, options.evictAfter); } /** * Matches a tain with any value of a specific key. */ static exists(key, options = {}) { return new NodeTaintQuery('Exists', key, undefined, options.effect, options.evictAfter); } /** * Matches any taint. */ static any() { return new NodeTaintQuery('Exists'); } /** * @internal */ _toKube() { return { effect: this.effect, key: this.key, operator: this.operator, tolerationSeconds: this.evictAfter?.toSeconds(), value: this.value, }; } } exports.NodeTaintQuery = NodeTaintQuery; _h = JSII_RTTI_SYMBOL_1; NodeTaintQuery[_h] = { fqn: "cdk8s-plus-25.NodeTaintQuery", version: "2.22.79" }; /** * Represents a group of pods. */ class Pods extends constructs_1.Construct { constructor(scope, id, expressions, labels, namespaces) { super(scope, id); this.expressions = expressions; this.labels = labels; this.namespaces = namespaces; } /** * Select pods in the cluster with various selectors. */ static select(scope, id, options) { return new Pods(scope, id, options.expressions, options.labels, options.namespaces); } /** * Select all pods. */ static all(scope, id, options = {}) { return Pods.select(scope, id, { namespaces: options.namespaces }); } /** * @see IPodSelector.toPodSelectorConfig() */ toPodSelectorConfig() { return { labelSelector: LabelSelector.of({ expressions: this.expressions, labels: this.labels }), namespaces: this.namespaces?.toNamespaceSelectorConfig(), }; } /** * @see INetworkPolicyPeer.toNetworkPolicyPeerConfig() */ toNetworkPolicyPeerConfig() { return { podSelector: this.toPodSelectorConfig() }; } /** * @see INetworkPolicyPeer.toPodSelector() */ toPodSelector() { return this; } } exports.Pods = Pods; _j = JSII_RTTI_SYMBOL_1; Pods[_j] = { fqn: "cdk8s-plus-25.Pods", version: "2.22.79" }; /** * A node that is matched by label selectors. */ class LabeledNode { constructor(labelSelector) { this.labelSelector = labelSelector; } ; } exports.LabeledNode = LabeledNode; _k = JSII_RTTI_SYMBOL_1; LabeledNode[_k] = { fqn: "cdk8s-plus-25.LabeledNode", version: "2.22.79" }; /** * A node that is matched by taint selectors. */ class TaintedNode { constructor(taintSelector) { this.taintSelector = taintSelector; } ; } exports.TaintedNode = TaintedNode; _l = JSII_RTTI_SYMBOL_1; TaintedNode[_l] = { fqn: "cdk8s-plus-25.TaintedNode", version: "2.22.79" }; /** * A node that is matched by its name. */ class NamedNode { constructor(name) { this.name = name; } ; } exports.NamedNode = NamedNode; _m = JSII_RTTI_SYMBOL_1; NamedNode[_m] = { fqn: "cdk8s-plus-25.NamedNode", version: "2.22.79" }; /** * Represents a node in the cluster. */ class Node { /** * Match a node by its labels. */ static labeled(...labelSelector) { return new LabeledNode(labelSelector); } /** * Match a node by its name. */ static named(nodeName) { return new NamedNode(nodeName); } /** * Match a node by its taints. */ static tainted(...taintSelector) { return new TaintedNode(taintSelector); } } exports.Node = Node; _o = JSII_RTTI_SYMBOL_1; Node[_o] = { fqn: "cdk8s-plus-25.Node", version: "2.22.79" }; /** * Available topology domains. */ class Topology { constructor(key) { this.key = key; } /** * Custom key for the node label that the system uses to denote the topology domain. */ static custom(key) { return new Topology(key); } ; } exports.Topology = Topology; _p = JSII_RTTI_SYMBOL_1; Topology[_p] = { fqn: "cdk8s-plus-25.Topology", version: "2.22.79" }; /** * A hostname represents a single node in the cluster. * * @see https://kubernetes.io/docs/reference/labels-annotations-taints/#kubernetesiohostname */ Topology.HOSTNAME = new Topology('kubernetes.io/hostname'); /** * A zone represents a logical failure domain. It is common for Kubernetes clusters to * span multiple zones for increased availability. While the exact definition of a zone is * left to infrastructure implementations, common properties of a zone include very low * network latency within a zone, no-cost network traffic within a zone, and failure * independence from other zones. For example, nodes within a zone might share a network * switch, but nodes in different zones should not. * * @see https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesiozone */ Topology.ZONE = new Topology('topology.kubernetes.io/zone'); /** * A region represents a larger domain, made up of one or more zones. It is uncommon * for Kubernetes clusters to span multiple regions. While the exact definition of a * zone or region is left to infrastructure implementations, common properties of a region * include higher network latency between them than within them, non-zero cost for network * traffic between them, and failure independence from other zones or regions. * * For example, nodes within a region might share power infrastructure (e.g. a UPS or generator), but * nodes in different regions typically would not. * * @see https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesioregion */ Topology.REGION = new Topology('topology.kubernetes.io/region'); /** * Controls the pod scheduling strategy. */ class PodScheduling { constructor(instance) { this.instance = instance; this._nodeAffinityPreferred = []; this._nodeAffinityRequired = []; this._podAffinityPreferred = []; this._podAffinityRequired = []; this._podAntiAffinityPreferred = []; this._podAntiAffinityRequired = []; this._tolerations = []; } /** * Assign this pod a specific node by name. * * The scheduler ignores the Pod, and the kubelet on the named node * tries to place the Pod on that node. Overrules any affinity rules of the pod. * * Some limitations of static assignment are: * * - If the named node does not exist, the Pod will not run, and in some * cases may be automatically deleted. * - If the named node does not have the resources to accommodate the Pod, * the Pod will fail and its reason will indicate why, for example OutOfmemory or OutOfcpu. * - Node names in cloud environments are not always predictable or stable. * * Will throw is the pod is already assigned to named node. * * Under the hood, this method utilizes the `nodeName` property. */ assign(node) { if (this._nodeName) { // disallow overriding an static node assignment throw new Error(`Cannot assign ${this.instance.podMetadata.name} to node ${node.name}. It is already assigned to node ${this._nodeName}`); } else { this._nodeName = node.name; } } /** * Allow this pod to tolerate taints matching these tolerations. * * You can put multiple taints on the same node and multiple tolerations on the same pod. * The way Kubernetes processes multiple taints and tolerations is like a filter: start with * all of a node's taints, then ignore the ones for which the pod has a matching toleration; * the remaining un-ignored taints have the indicated effects on the pod. In particular: * * - if there is at least one un-ignored taint with effect NoSchedule then Kubernetes will * not schedule the pod onto that node * - if there is no un-ignored taint with effect NoSchedule but there is at least one un-ignored * taint with effect PreferNoSchedule then Kubernetes will try to not schedule the pod onto the node * - if there is at least one un-ignored taint with effect NoExecute then the pod will be evicted from * the node (if it is already running on the node), and will not be scheduled onto the node (if it is * not yet running on the node). * * Under the hood, this method utilizes the `tolerations` property. * * @see https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ */ tolerate(node) { for (const query of node.taintSelector) { this._tolerations.push(query._toKube()); } } /** * Attract this pod to a node matched by selectors. * You can select a node by using `Node.labeled()`. * * Attracting to multiple nodes (i.e invoking this method multiple times) acts as * an OR condition, meaning the pod will be assigned to either one of the nodes. * * Under the hood, this method utilizes the `nodeAffinity` property. * * @see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity */ attract(node, options = {}) { const term = this.createNodeAffinityTerm(node); if (options.weight) { this.validateWeight(options.weight); this._nodeAffinityPreferred.push({ weight: options.weight, preference: term }); } else { this._nodeAffinityRequired.push(term); } } /** * Co-locate this pod with a scheduling selection. * * A selection can be one of: * * - An instance of a `Pod`. * - An instance of a `Workload` (e.g `Deployment`, `StatefulSet`). * - An un-managed pod that can be selected via `Pods.select()`. * * Co-locating with multiple selections ((i.e invoking this method multiple times)) acts as * an AND condition. meaning the pod will be assigned to a node that satisfies all * selections (i.e runs at least one pod that satisifies each selection). * * Under the hood, this method utilizes the `podAffinity` property. * * @see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity */ colocate(selector, options = {}) { const topology = options.topology ?? Topology.HOSTNAME; const term = this.createPodAffinityTerm(topology, selector); if (options.weight) { this.validateWeight(options.weight); this._podAffinityPreferred.push({ weight: options.weight, podAffinityTerm: term }); } else { this._podAffinityRequired.push(term); } } /** * Seperate this pod from a scheduling selection. * * A selection can be one of: * * - An instance of a `Pod`. * - An instance of a `Workload` (e.g `Deployment`, `StatefulSet`). * - An un-managed pod that can be selected via `Pods.select()`. * * Seperating from multiple selections acts as an AND condition. meaning the pod * will not be assigned to a node that satisfies all selections (i.e runs at least one pod that satisifies each selection). * * Under the hood, this method utilizes the `podAntiAffinity` property. * * @see https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity */ separate(selector, options = {}) { const topology = options.topology ?? Topology.HOSTNAME; const term = this.createPodAffinityTerm(topology, selector); if (options.weight) { this.validateWeight(options.weight); this._podAntiAffinityPreferred.push({ weight: options.weight, podAffinityTerm: term }); } else { this._podAntiAffinityRequired.push(term); } } createPodAffinityTerm(topology, selector) { const config = selector.toPodSelectorConfig(); return { topologyKey: topology.key, labelSelector: config.labelSelector._toKube(), namespaceSelector: config.namespaces?.labelSelector?._toKube(), namespaces: config.namespaces?.names, }; } createNodeAffinityTerm(node) { return { matchExpressions: node.labelSelector.map(s => s._toKube()) }; } validateWeight(weight) { if (weight < 1 || weight > 100) { // https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity-weight throw new Error(`Invalid affinity weight: ${weight}. Must be in range 1-100`); } } /** * @internal */ _toKube() { const atLeastOne = (...arrays) => { return arrays.flat().length > 0; }; const hasNodeAffinity = atLeastOne(this._nodeAffinityPreferred, this._nodeAffinityRequired); const hasPodAffinity = atLeastOne(this._podAffinityPreferred, this._podAffinityRequired); const hasPodAntiAffinty = atLeastOne(this._podAntiAffinityPreferred, this._podAntiAffinityRequired); const hasAffinity = hasNodeAffinity || hasPodAffinity || hasPodAntiAffinty; return { affinity: hasAffinity ? { nodeAffinity: hasNodeAffinity ? { preferredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._nodeAffinityPreferred), requiredDuringSchedulingIgnoredDuringExecution: this._nodeAffinityRequired.length > 0 ? { nodeSelectorTerms: this._nodeAffinityRequired, } : undefined, } : undefined, podAffinity: hasPodAffinity ? { preferredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAffinityPreferred), requiredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAffinityRequired), } : undefined, podAntiAffinity: hasPodAntiAffinty ? { preferredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAntiAffinityPreferred), requiredDuringSchedulingIgnoredDuringExecution: utils_1.undefinedIfEmpty(this._podAntiAffinityRequired), } : undefined, } : undefined, nodeName: this._nodeName, tolerations: utils_1.undefinedIfEmpty(this._tolerations), }; } } exports.PodScheduling = PodScheduling; _q = JSII_RTTI_SYMBOL_1; PodScheduling[_q] = { fqn: "cdk8s-plus-25.PodScheduling", version: "2.22.79" }; /** * Isolation determines which policies are created * when allowing connections from a a pod / workload to peers. */ var PodConnectionsIsolation; (function (PodConnectionsIsolation) { /** * Only creates network policies that select the pod. */ PodConnectionsIsolation["POD"] = "POD"; /** * Only creates network policies that select the peer. */ PodConnectionsIsolation["PEER"] = "PEER"; })(PodConnectionsIsolation = exports.PodConnectionsIsolation || (exports.PodConnectionsIsolation = {})); /** * Controls network isolation rules for inter-pod communication. */ class PodConnections { constructor(instance) { this.instance = instance; } /** * Allow network traffic from this pod to the peer. * * By default, this will create an egress network policy for this pod, and an ingress * network policy for the peer. This is required if both sides are already isolated. * Use `options.isolation` to control this behavior. * * @example * * // create only an egress policy that selects the 'web' pod to allow outgoing traffic * // to the 'redis' pod. this requires the 'redis' pod to not be isolated for ingress. * web.connections.allowTo(redis, { isolation: Isolation.POD }) * * // create only an ingress policy that selects the 'redis' peer to allow incoming traffic * // from the 'web' pod. this requires the 'web' pod to not be isolated for egress. * web.connections.allowTo(redis, { isolation: Isolation.PEER }) * */ allowTo(peer, options = {}) { return this.allow('Egress', peer, { ports: this.extractPorts(peer), ...options }); } /** * Allow network traffic from the peer to this pod. * * By default, this will create an ingress network policy for this pod, and an egress * network policy for the peer. This is required if both sides are already isolated. * Use `options.isolation` to control this behavior. * * @example * * // create only an egress policy that selects the 'web' pod to allow outgoing traffic * // to the 'redis' pod. this requires the 'redis' pod to not be isolated for ingress. * redis.connections.allowFrom(web, { isolation: Isolation.PEER }) * * // create only an ingress policy that selects the 'redis' peer to allow incoming traffic * // from the 'web' pod. this requires the 'web' pod to not be isolated for egress. * redis.connections.allowFrom(web, { isolation: Isolation.POD }) * */ allowFrom(peer, options = {}) { return this.allow('Ingress', peer, { ports: this.extractPorts(this.instance), ...options }); } allow(direction, peer, options = {}) { const config = peer.toNetworkPolicyPeerConfig(); networkpolicy.validatePeerConfig(config); const peerAddress = utils_1.address(peer); if (!options.isolation || options.isolation === PodConnectionsIsolation.POD) { const src = new networkpolicy.NetworkPolicy(this.instance, `Allow${direction}${peerAddress}`, { selector: this.instance, // the policy must be defined in the namespace of the pod // so it can select it. metadata: { namespace: this.instance.metadata.namespace }, }); switch (direction) { case 'Egress': src.addEgressRule(peer, options.ports); break; case 'Ingress': src.addIngressRule(peer, options.ports); } } if (!options.isolation || options.isolation === PodConnectionsIsolation.PEER) { if (config.ipBlock) { // for an ip block we don't need to create the opposite policies return; } const podSelector = peer.toPodSelector(); if (!podSelector) { throw new Error(`Unable to create policies for peer '${peer.node.addr}' since its not a pod selector`); } const oppositeDirection = direction === 'Egress' ? 'Ingress' : 'Egress'; const podSelectorConfig = podSelector.toPodSelectorConfig(); let namespaces; if (!podSelectorConfig.namespaces) { // if the peer doesn't specify namespaces, we assume the same namespace. namespaces = [this.instance.metadata.namespace]; } else { // a peer cannot specify namespaces by labels because // we won't be able to extract the names of those namespaces. if (podSelectorConfig.namespaces.labelSelector && !podSelectorConfig.namespaces.labelSelector.isEmpty()) { throw new Error(`Unable to create an ${oppositeDirection} policy for peer '${peer.node.path}' (pod=${this.instance.name}). Peer must specify namespaces only by name`); } // a peer must specify namespaces by name. if (!podSelectorConfig.namespaces.names) { throw new Error(`Unable to create an ${oppositeDirection} policy for peer '${peer.node.path}' (pod=${this.instance.name}). Peer must specify namespace names`); } namespaces = podSelectorConfig.namespaces.names; } for (const name of namespaces) { switch (direction) { case 'Egress': new networkpolicy.NetworkPolicy(this.instance, `AllowIngress${name}${peerAddress}`, { selector: podSelector, metadata: { namespace: name }, ingress: { rules: [{ peer: this.instance, ports: options.ports }] }, }); break; case 'Ingress': new networkpolicy.NetworkPolicy(this.instance, `AllowEgress${name}${peerAddress}`, { selector: podSelector, metadata: { namespace: name }, egress: { rules: [{ peer: this.instance, ports: options.ports }] }, }); break; default: throw new Error(`Unsupported direction: ${direction}`); } } } } extractPorts(selector) { return container.extractContainerPorts(selector).map(n => networkpolicy.NetworkPolicyPort.tcp(n.number)); } /** * Sets the default network policy for Pod/Workload to have all egress and ingress connections as disabled */ isolate() { new networkpolicy.NetworkPolicy(this.instance, 'DefaultDenyAll', { selector: this.instance, // the policy must be defined in the namespace of the pod // so it can select it. metadata: { namespace: this.instance.metadata.namespace }, egress: { default: networkpolicy.NetworkPolicyTrafficDefault.DENY, }, ingress: { default: networkpolicy.NetworkPolicyTrafficDefault.DENY, }, }); } } exports.PodConnections = PodConnections; _r = JSII_RTTI_SYMBOL_1; PodConnections[_r] = { fqn: "cdk8s-plus-25.PodConnections", version: "2.22.79" }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3BvZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLGlDQUFzRjtBQUN0RiwyQ0FBbUQ7QUFDbkQsK0JBQStCO0FBQy9CLHlDQUF5QztBQUN6QyxxQ0FBcUM7QUFFckMsa0RBQWtEO0FBSWxELG1DQUFvRDtBQUdwRCxNQUFzQixXQUFZLFNBQVEsSUFBSSxDQUFDLFFBQVE7SUFvQnJELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsUUFBMEIsRUFBRTtRQUNwRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBUkYsZ0JBQVcsR0FBMEIsRUFBRSxDQUFDO1FBQ3hDLG9CQUFlLEdBQTBCLEVBQUUsQ0FBQztRQUM1QyxpQkFBWSxHQUFnQixFQUFFLENBQUM7UUFDL0IsYUFBUSxHQUErQixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBT2hFLElBQUksQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDLGFBQWEsSUFBSSxhQUFhLENBQUMsTUFBTSxDQUFDO1FBQ2pFLElBQUksQ0FBQyxjQUFjLEdBQUcsS0FBSyxDQUFDLGNBQWMsQ0FBQztRQUMzQyxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksa0JBQWtCLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBQ3JFLElBQUksQ0FBQyxHQUFHLEdBQUcsSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2pDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxLQUFLLENBQUMsa0JBQWtCLENBQUM7UUFDbkQsSUFBSSxDQUFDLDRCQUE0QixHQUFHLEtBQUssQ0FBQyw0QkFBNEIsSUFBSSxLQUFLLENBQUM7UUFDaEYsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxJQUFJLEtBQUssQ0FBQztRQUN0QyxJQUFJLENBQUMsV0FBVyxHQUFHLEtBQUssQ0FBQyxXQUFXLElBQUksS0FBSyxDQUFDO1FBQzlDLElBQUksQ0FBQyxzQkFBc0IsR0FBRyxLQUFLLENBQUMsc0JBQXNCLElBQUksZ0JBQVEsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLENBQUM7UUFFbkYsSUFBSSxLQUFLLENBQUMsVUFBVSxFQUFFO1lBQ3BCLEtBQUssQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQ3JEO1FBRUQsSUFBSSxLQUFLLENBQUMsT0FBTyxFQUFFO1lBQ2pCLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQy9DO1FBRUQsSUFBSSxLQUFLLENBQUMsY0FBYyxFQUFFO1lBQ3hCLEtBQUssQ0FBQyxjQUFjLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDN0Q7UUFFRCxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUU7WUFDckIsS0FBSyxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDdEQ7SUFFSCxDQUFDO0lBRUQsSUFBVyxVQUFVO1FBQ25CLE9BQU8sQ0FBQyxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUMvQixDQUFDO0lBRUQsSUFBVyxjQUFjO1FBQ3ZCLE9BQU8sQ0FBQyxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQsSUFBVyxPQUFPO1FBQ2hCLE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVELElBQVcsV0FBVztRQUNwQixPQUFPLENBQUMsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDaEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksbUJBQW1CO1FBQ3hCLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUNoRSxJQUFJLENBQUMsVUFBVSxFQUFFO1lBQ2YsdUZBQXVGO1lBQ3ZGLE1BQU0sSUFBSSxLQUFLLENBQUMsMkNBQTJDLEdBQUcsQ0FBQyxhQUFhLG1CQUFtQixDQUFDLENBQUM7U0FDbEc7UUFDRCxPQUFPO1lBQ0wsYUFBYSxFQUFFLGFBQWEsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsRUFBRSxVQUFVLEVBQUUsRUFBRSxDQUFDO1lBQ2hGLFVBQVUsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7Z0JBQ3BDLEtBQUssRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDO2FBQ2pDLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDZCxDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0kseUJBQXlCO1FBQzlCLE9BQU8sRUFBRSxXQUFXLEVBQUUsSUFBSSxDQUFDLG1CQUFtQixFQUFFLEVBQUUsQ0FBQztJQUNyRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxhQUFhO1FBQ2xCLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVNLFlBQVksQ0FBQyxJQUE4QjtRQUNoRCxNQUFNLElBQUksR0FBRyxJQUFJLFNBQVMsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMzQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFTSxlQUFlLENBQUMsSUFBeUI7UUFDOUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUIsQ0FBQztJQUVNLGdCQUFnQixDQUFDLElBQThCO1FBRXBELDBHQUEwRztRQUMxRyxJQUFJLElBQUksQ0FBQyxTQUFTLEVBQUU7WUFDbEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxpREFBaUQsQ0FBQyxDQUFDO1NBQ3BFO1FBRUQsSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0RBQWdELENBQUMsQ0FBQztTQUNuRTtRQUVELElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLCtDQUErQyxDQUFDLENBQUM7U0FDbEU7UUFFRCxNQUFNLElBQUksR0FBRyxJQUFJLFNBQVMsQ0FBQyxTQUFTLENBQUM7WUFDbkMsR0FBRyxJQUFJO1lBQ1AsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLElBQUksUUFBUSxJQUFJLENBQUMsZUFBZSxDQUFDLE1BQU0sRUFBRTtTQUN6RCxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoQyxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFTSxZQUFZLENBQUMsU0FBb0I7UUFDdEMsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDcEMsQ0FBQztJQUVNLFNBQVMsQ0FBQyxHQUFrQjtRQUNqQyxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbkQsSUFBSSxjQUFjLEVBQUU7WUFDbEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxvQkFBb0IsR0FBRyxDQUFDLElBQUksaUJBQWlCLENBQUMsQ0FBQztTQUNoRTtRQUNELElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDbkMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksc0JBQXNCO1FBRTNCLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxJQUFJLENBQUMsSUFBSSxDQUFDLDRCQUE0QixFQUFFO1lBQzlELE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxpREFBaUQ7a0JBQ3pFLGdHQUFnRyxDQUFDLENBQUM7U0FDdkc7UUFFRCxxRUFBcUU7UUFDckUsa0JBQWtCO1FBQ2xCLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxDQUFDLGNBQWMsRUFBRSxJQUFJLElBQUksU0FBUyxDQUFDO1FBRWxFLE9BQU87WUFDTCxJQUFJLEVBQUUsZ0JBQWdCO1lBQ3RCLElBQUksRUFBRSxrQkFBa0I7WUFDeEIsUUFBUSxFQUFFLEVBQUU7U0FDYixDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksVUFBVTtRQUVmLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1lBQ2hDLE1BQU0sSUFBSSxLQUFLLENBQUMsd0NBQXdDLENBQUMsQ0FBQztTQUMzRDtRQUVELE1BQU0sT0FBTyxHQUErQixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ3RELE1BQU0sVUFBVSxHQUFvQixFQUFFLENBQUM7UUFDdkMsTUFBTSxjQUFjLEdBQW9CLEVBQUUsQ0FBQztRQUUzQyxLQUFLLE1BQU0sSUFBSSxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUU7WUFDbEMsb0RBQW9EO1lBQ3BELHVEQUF1RDtZQUN2RCxLQUFLLE1BQU0sS0FBSyxJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQy9CLFNBQVMsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7YUFDekI7WUFDRCxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1NBQ2pDO1FBRUQsS0FBSyxNQUFNLElBQUksSUFBSSxJQUFJLENBQUMsY0FBYyxFQUFFO1lBQ3RDLG9EQUFvRDtZQUNwRCx1REFBdUQ7WUFDdkQsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFO2dCQUMvQixTQUFTLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO2FBQ3pCO1lBQ0QsY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztTQUNyQztRQUVELEtBQUssTUFBTSxHQUFHLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUM5QixTQUFTLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEI7UUFFRCxTQUFTLFNBQVMsQ0FBQyxHQUFrQjtZQUNuQyxNQUFNLGNBQWMsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUM3QywyRUFBMkU7WUFDM0UsMERBQTBEO1lBQzFELElBQUksY0FBYyxJQUFJLGNBQWMsS0FBSyxHQUFHLEVBQUU7Z0JBQzVDLE1BQU0sSUFBSSxLQUFLLENBQUMsbUZBQW1GLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO2FBQ2hIO1lBQ0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQzdCLENBQUM7UUFFRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBRS9CLE9BQU87WUFDTCxhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWE7WUFDakMsa0JBQWtCLEVBQUUsSUFBSSxDQUFDLGNBQWMsRUFBRSxJQUFJO1lBQzdDLFVBQVUsRUFBRSxVQUFVO1lBQ3RCLGVBQWUsRUFBRSx3QkFBZ0IsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2pFLGNBQWMsRUFBRSx3QkFBZ0IsQ0FBQyxjQUFjLENBQUM7WUFDaEQsV0FBVyxFQUFFLHdCQUFnQixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUM7WUFDL0MsT0FBTyxFQUFFLHdCQUFnQixDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7WUFDN0UsU0FBUyxFQUFFLEdBQUcsQ0FBQyxNQUFNO1lBQ3JCLFNBQVMsRUFBRSx3QkFBZ0IsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDO1lBQ3ZDLFFBQVEsRUFBRSxHQUFHLENBQUMsUUFBUTtZQUN0QixTQUFTLEVBQUUsR0FBRyxDQUFDLFNBQVM7WUFDeEIsaUJBQWlCLEVBQUUsR0FBRyxDQUFDLGNBQWM7WUFDckMsZ0JBQWdCLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxr