cdk-eks-karpenter
Version:
CDK construct library that allows you install Karpenter in an AWS EKS cluster
537 lines • 72.9 kB
JavaScript
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Karpenter = void 0;
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const aws_cdk_lib_1 = require("aws-cdk-lib");
const aws_events_1 = require("aws-cdk-lib/aws-events");
const aws_events_targets_1 = require("aws-cdk-lib/aws-events-targets");
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
const aws_sqs_1 = require("aws-cdk-lib/aws-sqs");
const constructs_1 = require("constructs");
const semver = require("semver");
const utils_1 = require("./utils");
class Karpenter extends constructs_1.Construct {
constructor(scope, id, props) {
super(scope, id);
this.cluster = props.cluster;
this.namespace = props.namespace ?? 'karpenter';
this.serviceAccountName = props.serviceAccountName ?? 'karpenter';
this.version = props.version;
this.helmExtraValues = props.helmExtraValues ?? {};
this.controllerIAMPolicyStatements = [];
this.interruptionQueue = undefined;
this.helmChartValues = {
settings: {
aws: {},
},
};
/*
* We create a node role for Karpenter managed nodes, alongside an instance profile for the EC2
* instances that will be managed by karpenter.
*
* We will also create a role mapping in the `aws-auth` ConfigMap so that the nodes can authenticate
* with the Kubernetes API using IAM.
*
* Create Node Role if nodeRole not added as prop
* Make sure that the Role that is added does not have an Instance Profile associated to it
* since we will create it here.
*/
if (!props.nodeRole) {
this.nodeRole = new aws_iam_1.Role(this, 'NodeRole', {
assumedBy: new aws_iam_1.ServicePrincipal(`ec2.${aws_cdk_lib_1.Aws.URL_SUFFIX}`),
managedPolicies: [
aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKS_CNI_Policy'),
aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSWorkerNodePolicy'),
aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'),
aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
],
});
}
else {
this.nodeRole = props.nodeRole;
}
const instanceProfile = new aws_iam_1.CfnInstanceProfile(this, 'InstanceProfile', {
roles: [this.nodeRole.roleName],
});
this.cluster.awsAuth.addRoleMapping(this.nodeRole, {
username: 'system:node:{{EC2PrivateDNSName}}',
groups: [
'system:bootstrappers',
'system:nodes',
],
});
/**
* For the Karpenter controller to be able to talk to the AWS APIs, we need to set up a few
* resources which will allow the Karpenter controller to use IAM Roles for Service Accounts
*/
this.serviceAccount = this.cluster.addServiceAccount('karpenter', {
name: this.serviceAccountName,
namespace: this.namespace,
});
// Setup the controller IAM Policy statements
this.addControllerPolicyIAMPolicyStatements();
// Set repoUrl based on which version of Karpenter we want to install
const repoUrl = this.helmRepoURLFromKarpenterVersion();
// Setup the interruption queue, different helm chart versions have different key names
this.interruptionQueue = this.addInterruptionQueue();
// Manage different helm values depending on Karpenter version
if (semver.gte(this.version, 'v0.19.0') && semver.lte(this.version, 'v0.32.0')) {
this.helmChartValues.settings.aws = {
clusterName: this.cluster.clusterName,
clusterEndpoint: this.cluster.clusterEndpoint,
defaultInstanceProfile: instanceProfile.ref,
interruptionQueueName: this.interruptionQueue?.queueName,
};
}
if (semver.gte(this.version, 'v0.32.0')) {
this.helmChartValues.settings = {
clusterName: this.cluster.clusterName,
clusterEndpoint: this.cluster.clusterEndpoint,
interruptionQueue: this.interruptionQueue?.queueName,
};
}
// These are fixed values that we supply to the Helm Chart.
this.helmChartValues = {
...{
serviceAccount: {
create: false,
name: this.serviceAccount.serviceAccountName,
annotations: {
'eks.amazonaws.com/role-arn': this.serviceAccount.role.roleArn,
},
},
},
...this.helmChartValues,
};
new aws_iam_1.Policy(this, 'ControllerPolicy', {
roles: [this.serviceAccount.role],
statements: this.controllerIAMPolicyStatements,
});
/**
* Finally, we can go ahead and install the Helm chart provided for Karpenter with the inputs
* we desire.
*/
this.chart = this.cluster.addHelmChart('karpenter', {
// This one is important, if we don't ask helm to wait for resources to become available, the
// subsequent creation of karpenter resources will fail.
wait: true,
chart: 'karpenter',
release: 'karpenter',
repository: repoUrl,
namespace: this.namespace,
version: this.version,
createNamespace: false,
// We will merge our dyanmic `helmExtraValues` with the fixed values. Where the fixed values
// will override the dynamic values.
values: { ...this.helmExtraValues, ...this.helmChartValues },
});
// If we are not installing it in the `kube-system` namespace:
// Note: We should be installing it in kube-system, please see: https://github.com/aws/karpenter-provider-aws/blob/fd2b60759f81dc0c868810cc44443103067c4880/website/content/en/v0.36/upgrading/upgrade-guide.md?plain=1#L91
// Also see https://github.com/aws-samples/cdk-eks-karpenter/issues/189 and https://github.com/aws-samples/cdk-eks-karpenter/issues/173
if (this.namespace != 'kube-system') {
const namespace = this.cluster.addManifest('karpenter-namespace', {
apiVersion: 'v1',
kind: 'Namespace',
metadata: {
name: this.namespace,
},
});
// If we are creating a namespace, we need to link it to the service account and the chart, so they are deployed in the correct order.
this.serviceAccount.node.addDependency(namespace);
this.chart.node.addDependency(namespace);
}
}
/**
* addEC2NodeClass adds a EC2NodeClass to the Karpenter configuration.
*
* @param id must consist of lower case alphanumeric characters, \'-\' or \'.\', and must start and end with an alphanumeric character
* @param ec2NodeClassSpec spec of Karpenters EC2NodeClass API
*
* @returns the metadata object of the created manifest
*/
addEC2NodeClass(id, ec2NodeClassSpec) {
// Validate the name of the resource
if (!utils_1.Utils.validateKubernetesNameConformance(id)) {
throw new Error('name does not conform to k8s policy');
}
// Ensure we provide a valid spec
utils_1.Utils.hasRequiredKeys(ec2NodeClassSpec, [
'amiFamily', 'subnetSelectorTerms', 'securityGroupSelectorTerms', 'role',
]);
return this.addManifest(id, {
apiVersion: 'karpenter.k8s.aws/v1',
kind: 'EC2NodeClass',
metadata: {
name: id,
namespace: this.namespace,
},
spec: ec2NodeClassSpec,
});
}
/**
* addNodePool adds a NodePool to the Karpenter configuration.
*
* @param id must consist of lower case alphanumeric characters, \'-\' or \'.\', and must start and end with an alphanumeric character
* @param nodePoolSpec spec of Karpenters NodePool API
*
* @returns the metadata object of the created manifest
*/
addNodePool(id, nodePoolSpec) {
// Validate the name of the resource
if (!utils_1.Utils.validateKubernetesNameConformance(id)) {
throw new Error('name does not conform to k8s policy');
}
// Ensure we provide a valid spec
utils_1.Utils.hasRequiredKeys(nodePoolSpec.template.spec, ['nodeClassRef', 'requirements']);
return this.addManifest(id, {
apiVersion: 'karpenter.sh/v1',
kind: 'NodePool',
metadata: {
name: id,
namespace: this.namespace,
},
spec: nodePoolSpec,
});
}
/**
* addProvisioner adds a provisioner manifest to the cluster. Currently the provisioner spec
* parameter is relatively free form.
*
* @param id - must consist of lower case alphanumeric characters, \'-\' or \'.\', and must start and end with an alphanumeric character
* @param provisionerSpec - spec of Karpenters Provisioner object.
*
* @deprecated This method should not be used with Karpenter >v0.32.0
*/
addProvisioner(id, provisionerSpec) {
// Validate the name of the resource
if (!utils_1.Utils.validateKubernetesNameConformance(id)) {
throw new Error('name does not conform to k8s policy');
}
// If later than version v0.32.0, we should throw an exception here as the APIs
// changed after that version and this method should not be used.
if (semver.gte('v0.32.0', this.version)) {
throw new Error('This method is not supported for this Karpenter version. Please use addEC2NodeClass instead.');
}
this.addManifest(id, {
apiVersion: 'karpenter.k8s.aws/v1alpha5',
kind: 'Provisioner',
metadata: {
name: id,
namespace: this.namespace,
},
spec: provisionerSpec,
});
}
/**
* addNodeTemplate adds a node template manifest to the cluster. Currently the node template spec
* parameter is relatively free form.
*
* @param id - must consist of lower case alphanumeric characters, \'-\' or \'.\', and must start and end with an alphanumeric character
* @param nodeTemplateSpec - spec of Karpenters Node Template object.
*
* @deprecated This method should not be used with Karpenter >v0.32.0
*/
addNodeTemplate(id, nodeTemplateSpec) {
// Validate the name of the resource
if (!utils_1.Utils.validateKubernetesNameConformance(id)) {
throw new Error('name does not conform to k8s policy');
}
// If version >= v0.32.0, we should throw an exception here as the APIs
// changed after that version and this method should not be used.
if (semver.gte('v0.32.0', this.version)) {
throw new Error('This method is not supported for this Karpenter version. Please use addEC2NodeClass instead.');
}
this.addManifest(id, {
apiVersion: 'karpenter.k8s.aws/v1',
kind: 'AWSNodeTemplate',
metadata: {
namespace: this.namespace,
},
spec: nodeTemplateSpec,
});
}
/**
* addManifest crafts Kubernetes manifests for the specific APIs
*
* @param id
* @param apiVersion
* @param kind
* @param metadata
* @param spec
*
* @returns the metadata object of the created manifest
*/
addManifest(id, props) {
let defaultMetadata = {
name: id,
};
let m = {
apiVersion: props.apiVersion,
kind: props.kind,
metadata: {
// We will merge our metadata details. The parameters provided will be overwritten by
// defaultMetadata
...props.metadata, ...defaultMetadata,
},
spec: props.spec,
};
let manifstResource = this.cluster.addManifest(id, m);
manifstResource.node.addDependency(this.chart);
return m.metadata;
}
/**
* addManagedPolicyToKarpenterRole adds Managed Policies To Karpenter Role.
*
* @param managedPolicy - iam managed policy to add to the karpenter role.
*/
addManagedPolicyToKarpenterRole(managedPolicy) {
this.serviceAccount.role.addManagedPolicy(managedPolicy);
}
/**
* Get the Helm repo URL based on the Karpenter version
*
* @returns string
*/
helmRepoURLFromKarpenterVersion() {
if (semver.satisfies(this.version, '>= v0.17.0')) {
return 'oci://public.ecr.aws/karpenter/karpenter';
}
return 'https://charts.karpenter.sh';
}
/**
* addInterruptionQueue adds the interruption queue setup if neceesary
*/
addInterruptionQueue() {
if (semver.lte(this.version, 'v0.19.0')) {
return undefined;
}
;
// new version need SQS to handle the interruption
const interruptionQueue = new aws_sqs_1.Queue(this, 'KarpenterInterruptionQueue', {
queueName: this.cluster.clusterName,
retentionPeriod: aws_cdk_lib_1.Duration.minutes(5),
});
const rules = [
// ScheduledChangeRule
new aws_events_1.Rule(this, 'ScheduledChangeRule', {
eventPattern: {
source: ['aws.health'],
detailType: ['AWS Health Event'],
},
}),
// SpotInterruptionRule
new aws_events_1.Rule(this, 'SpotInterruptionRule', {
eventPattern: {
source: ['aws.ec2'],
detailType: ['EC2 Spot Instance Interruption Warning'],
},
}),
// RebalanceRule
new aws_events_1.Rule(this, 'RebalanceRule', {
eventPattern: {
source: ['aws.ec2'],
detailType: ['EC2 Instance Rebalance Recommendation'],
},
}),
// InstanceStateChangeRule
new aws_events_1.Rule(this, 'InstanceStateChangeRule', {
eventPattern: {
source: ['aws.ec2'],
detailType: ['EC2 Instance State-change Notification'],
},
}),
];
for (var rule of rules) {
rule.addTarget(new aws_events_targets_1.SqsQueue(interruptionQueue));
}
// new version need SQS privilege
this.controllerIAMPolicyStatements.push(new aws_iam_1.PolicyStatement({
actions: [
'sqs:DeleteMessage',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
'sqs:ReceiveMessage',
],
resources: [interruptionQueue.queueArn],
}));
return interruptionQueue;
}
/**
* Configure the IAM Policy StatementsPolicies for the Controller
* taken from https://raw.githubusercontent.com/aws/karpenter/v0.32.0/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml
*/
addControllerPolicyIAMPolicyStatements() {
const clusterName = this.cluster.clusterName;
const region = aws_cdk_lib_1.Aws.REGION;
const partition = aws_cdk_lib_1.Aws.PARTITION;
const accountId = aws_cdk_lib_1.Aws.ACCOUNT_ID;
const conditionCfnJson = (id, value) => new aws_cdk_lib_1.CfnJson(this, id, { value: value });
this.controllerIAMPolicyStatements.push(new aws_iam_1.PolicyStatement({
sid: 'AllowScopedEC2InstanceAccessActions',
resources: [
`arn:${partition}:ec2:${region}::image/*`,
`arn:${partition}:ec2:${region}::snapshot/*`,
`arn:${partition}:ec2:${region}:*:security-group/*`,
`arn:${partition}:ec2:${region}:*:subnet/*`,
],
actions: ['ec2:RunInstances', 'ec2:CreateFleet'],
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedEC2LaunchTemplateAccessActions',
resources: [`arn:${partition}:ec2:${region}:*:launch-template/*`],
actions: ['ec2:RunInstances', 'ec2:CreateFleet'],
conditions: {
StringEquals: conditionCfnJson('AllowScopedEC2InstanceAccessActions-ResourceTagClusterName', {
[`aws:ResourceTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
}),
StringLike: conditionCfnJson('AllowScopedEC2InstanceAccessActions-ResourceTagNodePool', {
'aws:ResourceTag/karpenter.sh/nodepool': '*',
}),
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedEC2InstanceActionsWithTags',
resources: [
`arn:${partition}:ec2:${region}:*:fleet/*`,
`arn:${partition}:ec2:${region}:*:instance/*`,
`arn:${partition}:ec2:${region}:*:volume/*`,
`arn:${partition}:ec2:${region}:*:network-interface/*`,
`arn:${partition}:ec2:${region}:*:launch-template/*`,
`arn:${partition}:ec2:${region}:*:spot-instances-request/*`,
],
actions: ['ec2:RunInstances', 'ec2:CreateFleet', 'ec2:CreateLaunchTemplate'],
conditions: {
StringEquals: conditionCfnJson('AllowScopedEC2InstanceActionsWithTags-1', {
[`aws:RequestTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
'aws:RequestTag/eks:eks-cluster-name': clusterName,
}),
StringLike: conditionCfnJson('AllowScopedEC2InstanceActionsWithTags-2', {
'aws:RequestTag/karpenter.sh/nodepool': '*',
}),
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedResourceCreationTagging',
resources: [
`arn:${partition}:ec2:${region}:*:fleet/*`,
`arn:${partition}:ec2:${region}:*:instance/*`,
`arn:${partition}:ec2:${region}:*:volume/*`,
`arn:${partition}:ec2:${region}:*:network-interface/*`,
`arn:${partition}:ec2:${region}:*:launch-template/*`,
`arn:${partition}:ec2:${region}:*:spot-instances-request/*`,
],
actions: ['ec2:CreateTags'],
conditions: {
StringEquals: conditionCfnJson('AllowScopedResourceCreationTagging-1', {
[`aws:RequestTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
'aws:RequestTag/eks:eks-cluster-name': clusterName,
'ec2:CreateAction': ['RunInstances', 'CreateFleet', 'CreateLaunchTemplate'],
}),
StringLike: conditionCfnJson('AllowScopedResourceCreationTagging-2', {
'aws:RequestTag/karpenter.sh/nodepool': '*',
}),
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedResourceTagging',
resources: [`arn:${partition}:ec2:${region}:*:instance/*`],
actions: ['ec2:CreateTags'],
conditions: {
'StringEquals': conditionCfnJson('AllowScopedResourceTagging-1', {
[`aws:ResourceTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
}),
'StringLike': conditionCfnJson('AllowScopedResourceTagging-2', {
'aws:ResourceTag/karpenter.sh/nodepool': '*',
}),
'StringEqualsIfExists': conditionCfnJson('AllowScopedResourceTagging-3', {
'aws:RequestTag/eks:eks-cluster-name': clusterName,
}),
'ForAllValues:StringEquals': conditionCfnJson('AllowScopedResourceTagging-4', {
'aws:TagKeys': ['eks:eks-cluster-name', 'karpenter.sh/nodeclaim', 'Name'],
}),
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedDeletion',
resources: [
`arn:${partition}:ec2:${region}:*:instance/*`,
`arn:${partition}:ec2:${region}:*:launch-template/*`,
],
actions: ['ec2:TerminateInstances', 'ec2:DeleteLaunchTemplate'],
conditions: {
StringEquals: conditionCfnJson('AllowScopedDeletion-1', {
[`aws:ResourceTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
}),
StringLike: conditionCfnJson('AllowScopedDeletion-2', {
'aws:ResourceTag/karpenter.sh/nodepool': '*',
}),
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowRegionalReadActions',
resources: ['*'],
actions: [
'ec2:DescribeImages',
'ec2:DescribeInstances',
'ec2:DescribeInstanceTypeOfferings',
'ec2:DescribeInstanceTypes',
'ec2:DescribeLaunchTemplates',
'ec2:DescribeSecurityGroups',
'ec2:DescribeSpotPriceHistory',
'ec2:DescribeSubnets',
],
conditions: {
StringEquals: {
'aws:RequestedRegion': region,
},
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowSSMReadActions',
resources: [`arn:${partition}:ssm:${region}::parameter/aws/service/*`],
actions: ['ssm:GetParameter'],
}), new aws_iam_1.PolicyStatement({
sid: 'AllowPricingReadActions',
resources: ['*'],
actions: ['pricing:GetProducts'],
}), new aws_iam_1.PolicyStatement({
sid: 'AllowPassingInstanceRole',
resources: [this.nodeRole.roleArn],
actions: ['iam:PassRole'],
conditions: {
StringEquals: {
'iam:PassedToService': ['ec2.amazonaws.com', 'ec2.amazonaws.com.cn'],
},
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedInstanceProfileCreationActions',
resources: [`arn:${partition}:iam::${accountId}:instance-profile/*`],
actions: ['iam:CreateInstanceProfile'],
conditions: {
StringEquals: conditionCfnJson('AllowScopedInstanceProfileCreationActions-1', {
[`aws:RequestTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
'aws:RequestTag/eks:eks-cluster-name': clusterName,
'aws:RequestTag/topology.kubernetes.io/region': region,
}),
StringLike: conditionCfnJson('AllowScopedInstanceProfileCreationActions-2', {
'aws:RequestTag/karpenter.k8s.aws/ec2nodeclass': '*',
}),
},
}), new aws_iam_1.PolicyStatement({
sid: 'AllowScopedInstanceProfileTagActions',
resources: [`arn:${partition}:iam::${accountId}:instance-profile/*`],
actions: ['iam:TagInstanceProfile'],
conditions: {
StringEquals: conditionCfnJson('AllowScopedInstanceProfileTagActions-1', {
[`aws:ResourceTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
'aws:ResourceTag/topology.kubernetes.io/region': region,
[`aws:RequestTag/kubernetes.io/cluster/${clusterName}`]: 'owned',
'aws:RequestTag/eks:eks-cluster-name': clusterName,
'aws:RequestTag/topology.kubernetes.io/region': region,
}),
StringLike: conditionCfnJson('AllowScopedInstanceProfileTagActions-2', {
'aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass': '*',
'aws:RequestTag/karpenter.k8s.aws/ec2nodeclass': '*',
}),
},
}));
}
}
exports.Karpenter = Karpenter;
_a = JSII_RTTI_SYMBOL_1;
Karpenter[_a] = { fqn: "cdk-eks-karpenter.Karpenter", version: "1.0.25" };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2Q0FBcUQ7QUFFckQsdURBQThDO0FBQzlDLHVFQUEwRDtBQUMxRCxpREFBeUk7QUFDekksaURBQW9EO0FBQ3BELDJDQUF1QztBQUN2QyxpQ0FBaUM7QUFDakMsbUNBQWdDO0FBd0NoQyxNQUFhLFNBQVUsU0FBUSxzQkFBUztJQWN0QyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXFCO1FBQzdELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDLFNBQVMsSUFBSSxXQUFXLENBQUM7UUFDaEQsSUFBSSxDQUFDLGtCQUFrQixHQUFHLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxXQUFXLENBQUM7UUFDbEUsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksQ0FBQyxlQUFlLEdBQUcsS0FBSyxDQUFDLGVBQWUsSUFBSSxFQUFFLENBQUM7UUFFbkQsSUFBSSxDQUFDLDZCQUE2QixHQUFHLEVBQUUsQ0FBQztRQUN4QyxJQUFJLENBQUMsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO1FBRW5DLElBQUksQ0FBQyxlQUFlLEdBQUc7WUFDckIsUUFBUSxFQUFFO2dCQUNSLEdBQUcsRUFBRSxFQUFFO2FBQ1I7U0FDRixDQUFDO1FBRUY7Ozs7Ozs7Ozs7VUFVRTtRQUNGLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDcEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLGNBQUksQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFO2dCQUN6QyxTQUFTLEVBQUUsSUFBSSwwQkFBZ0IsQ0FBQyxPQUFPLGlCQUFHLENB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QXFDLEVBQUUsV0FBVztvQkFDbEQsOENBQThDLEVBQUUsTUFBTTtpQkFDdkQsQ0FBQztnQkFDRixVQUFVLEVBQUUsZ0JBQWdCLENBQUMsNkNBQTZDLEVBQUU7b0JBQzFFLCtDQUErQyxFQUFFLEdBQUc7aUJBQ3JELENBQUM7YUFDSDtTQUNGLENBQUMsRUFDRixJQUFJLHlCQUFlLENBQUM7WUFDbEIsR0FBRyxFQUFFLHNDQUFzQztZQUMzQyxTQUFTLEVBQUUsQ0FBQyxPQUFPLFNBQVMsU0FBUyxTQUFTLHFCQUFxQixDQUFDO1lBQ3BFLE9BQU8sRUFBRSxDQUFDLHdCQUF3QixDQUFDO1lBQ25DLFVBQVUsRUFBRTtnQkFDVixZQUFZLEVBQUUsZ0JBQWdCLENBQUMsd0NBQXdDLEVBQUU7b0JBQ3ZFLENBQUMseUNBQXlDLFdBQVcsRUFBRSxDQUFDLEVBQUUsT0FBTztvQkFDakUsK0NBQStDLEVBQUUsTUFBTTtvQkFDdkQsQ0FBQyx3Q0FBd0MsV0FBVyxFQUFFLENBQUMsRUFBRSxPQUFPO29CQUNoRSxxQ0FBcUMsRUFBRSxXQUFXO29CQUNsRCw4Q0FBOEMsRUFBRSxNQUFNO2lCQUN2RCxDQUFDO2dCQUNGLFVBQVUsRUFBRSxnQkFBZ0IsQ0FBQyx3Q0FBd0MsRUFBRTtvQkFDckUsZ0RBQWdELEVBQUUsR0FBRztvQkFDckQsK0NBQStDLEVBQUUsR0FBRztpQkFDckQsQ0FBQzthQUNIO1NBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDUixDQUFDOztBQWpsQkgsOEJBa2xCQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEF3cywgQ2ZuSnNvbiwgRHVyYXRpb24gfSBmcm9tICdhd3MtY2RrLWxpYic7XG5pbXBvcnQgeyBDbHVzdGVyLCBIZWxtQ2hhcnQgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtZWtzJztcbmltcG9ydCB7IFJ1bGUgfSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtZXZlbnRzJztcbmltcG9ydCB7IFNxc1F1ZXVlIH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWV2ZW50cy10YXJnZXRzJztcbmltcG9ydCB7IENmbkluc3RhbmNlUHJvZmlsZSwgSU1hbmFnZWRQb2xpY3ksIE1hbmFnZWRQb2xpY3ksIFBvbGljeSwgUG9saWN5U3RhdGVtZW50LCBSb2xlLCBTZXJ2aWNlUHJpbmNpcGFsIH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWlhbSc7XG5pbXBvcnQgeyBJUXVldWUsIFF1ZXVlIH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLXNxcyc7XG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tICdjb25zdHJ1Y3RzJztcbmltcG9ydCAqIGFzIHNlbXZlciBmcm9tICdzZW12ZXInO1xuaW1wb3J0IHsgVXRpbHMgfSBmcm9tICcuL3V0aWxzJztcblxuZXhwb3J0IGludGVyZmFjZSBLYXJwZW50ZXJQcm9wcyB7XG4gIC8qKlxuICAgKiBUaGUgRUtTIENsdXN0ZXIgdG8gYXR0YWNoIHRvXG4gICAqL1xuICByZWFkb25seSBjbHVzdGVyOiBDbHVzdGVyO1xuXG4gIC8qKlxuICAgKiBUaGUgS3ViZXJuZXRlcyBuYW1lc3BhY2UgdG8gaW5zdGFsbCB0b1xuICAgKlxuICAgKiBAZGVmYXVsdCBrYXJwZW50ZXJcbiAgICovXG4gIHJlYWRvbmx5IG5hbWVzcGFjZT86IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIEt1YmVybmV0ZXMgU2VydmljZUFjY291bnQgbmFtZSB0byB1c2VcbiAgICpcbiAgICogQGRlZmF1bHQga2FycGVudGVyXG4gICAqL1xuICByZWFkb25seSBzZXJ2aWNlQWNjb3VudE5hbWU/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBoZWxtIGNoYXJ0IHZlcnNpb24gdG8gaW5zdGFsbFxuICAgKlxuICAgKiBAZGVmYXVsdCAtIGxhdGVzdFxuICAgKi9cbiAgcmVhZG9ubHkgdmVyc2lvbjogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBFeHRyYSB2YWx1ZXMgdG8gcGFzcyB0byB0aGUgS2FycGVudGVyIEhlbG0gY2hhcnRcbiAgICovXG4gIHJlYWRvbmx5IGhlbG1FeHRyYVZhbHVlcz86IFJlY29yZDxzdHJpbmcsIGFueT47XG5cbiAgLyoqXG4gICAqIEN1c3RvbSBOb2RlUm9sZSB0byBwYXNzIGZvciBLYXJwZW50ZXIgTm9kZXNcbiAgICovXG4gIHJlYWRvbmx5IG5vZGVSb2xlPzogUm9sZTtcbn1cblxuZXhwb3J0IGNsYXNzIEthcnBlbnRlciBleHRlbmRzIENvbnN0cnVjdCB7XG4gIHB1YmxpYyByZWFkb25seSBjbHVzdGVyOiBDbHVzdGVyO1xuICBwdWJsaWMgcmVhZG9ubHkgbmFtZXNwYWNlOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSBzZXJ2aWNlQWNjb3VudE5hbWU6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IHZlcnNpb246IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IG5vZGVSb2xlOiBSb2xlO1xuICBwdWJsaWMgcmVhZG9ubHkgaGVsbUV4dHJhVmFsdWVzOiBhbnk7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBjaGFydDogSGVsbUNoYXJ0O1xuICBwcml2YXRlIHJlYWRvbmx5IHNlcnZpY2VBY2NvdW50OiBhbnk7XG4gIHB1YmxpYyBoZWxtQ2hhcnRWYWx1ZXM6IFJlY29yZDxzdHJpbmcsIGFueT47XG4gIHByaXZhdGUgY29udHJvbGxlcklBTVBvbGljeVN0YXRlbWVudHM6IFBvbGljeVN0YXRlbWVudFtdO1xuICBwcml2YXRlIGludGVycnVwdGlvblF1ZXVlOiBJUXVldWUgfCB1bmRlZmluZWQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IEthcnBlbnRlclByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIHRoaXMuY2x1c3RlciA9IHByb3BzLmNsdXN0ZXI7XG4gICAgdGhpcy5uYW1lc3BhY2UgPSBwcm9wcy5uYW1lc3BhY2UgPz8gJ2thcnBlbnRlcic7XG4gICAgdGhpcy5zZXJ2aWNlQWNjb3VudE5hbWUgPSBwcm9wcy5zZXJ2aWNlQWNjb3VudE5hbWUgPz8gJ2thcnBlbnRlcic7XG4gICAgdGhpcy52ZXJzaW9uID0gcHJvcHMudmVyc2lvbjtcbiAgICB0aGlzLmhlbG1FeHRyYVZhbHVlcyA9IHByb3BzLmhlbG1FeHRyYVZhbHVlcyA/PyB7fTtcblxuICAgIHRoaXMuY29udHJvbGxlcklBTVBvbGljeVN0YXRlbWVudHMgPSBbXTtcbiAgICB0aGlzLmludGVycnVwdGlvblF1ZXVlID0gdW5kZWZpbmVkO1xuXG4gICAgdGhpcy5oZWxtQ2hhcnRWYWx1ZXMgPSB7XG4gICAgICBzZXR0aW5nczoge1xuICAgICAgICBhd3M6IHt9LFxuICAgICAgfSxcbiAgICB9O1xuXG4gICAgLypcbiAgICAgKiBXZSBjcmVhdGUgYSBub2RlIHJvbGUgZm9yIEthcnBlbnRlciBtYW5hZ2VkIG5vZGVzLCBhbG9uZ3NpZGUgYW4gaW5zdGFuY2UgcHJvZmlsZSBmb3IgdGhlIEVDMlxuICAgICAqIGluc3RhbmNlcyB0aGF0IHdpbGwgYmUgbWFuYWdlZCBieSBrYXJwZW50ZXIuXG4gICAgICpcbiAgICAgKiBXZSB3aWxsIGFsc28gY3JlYXRlIGEgcm9sZSBtYXBwaW5nIGluIHRoZSBgYXdzLWF1dGhgIENvbmZpZ01hcCBzbyB0aGF0IHRoZSBub2RlcyBjYW4gYXV0aGVudGljYXRlXG4gICAgICogd2l0aCB0aGUgS3ViZXJuZXRlcyBBUEkgdXNpbmcgSUFNLlxuICAgICAqXG4gICAgICogQ3JlYXRlIE5vZGUgUm9sZSBpZiBub2RlUm9sZSBub3QgYWRkZWQgYXMgcHJvcFxuICAgICAqIE1ha2Ugc3VyZSB0aGF0IHRoZSBSb2xlIHRoYXQgaXMgYWRkZWQgZG9lcyBub3QgaGF2ZSBhbiBJbnN0YW5jZSBQcm9maWxlIGFzc29jaWF0ZWQgdG8gaXRcbiAgICAgKiBzaW5jZSB3ZSB3aWxsIGNyZWF0ZSBpdCBoZXJlLlxuICAgICovXG4gICAgaWYgKCFwcm9wcy5ub2RlUm9sZSkge1xuICAgICAgdGhpcy5ub2RlUm9sZSA9IG5ldyBSb2xlKHRoaXMsICdOb2RlUm9sZScsIHtcbiAgICAgICAgYXNzdW1lZEJ5OiBuZXcgU2VydmljZVByaW5jaXBhbChgZWMyLiR7QXdzLlVSTF9TVUZGSVh9YCksXG4gICAgICAgIG1hbmFnZWRQb2xpY2llczogW1xuICAgICAgICAgIE1hbmFnZWRQb2xpY3kuZnJvbUF3c01hbmFnZWRQb2xpY3lOYW1lKCdBbWF6b25FS1NfQ05JX1BvbGljeScpLFxuICAgICAgICAgIE1hbmFnZWRQb2xpY3kuZnJvbUF3c01hbmFnZWRQb2xpY3lOYW1lKCdBbWF6b25FS1NXb3JrZXJOb2RlUG9saWN5JyksXG4gICAgICAgICAgTWFuYWdlZFBvbGljeS5mcm9tQXdzTWFuYWdlZFBvbGljeU5hbWUoJ0FtYXpvbkVDMkNvbnRhaW5lclJlZ2lzdHJ5UmVhZE9ubHknKSxcbiAgICAgICAgICBNYW5hZ2VkUG9saWN5LmZyb21Bd3NNYW5hZ2VkUG9saWN5TmFtZSgnQW1hem9uU1NNTWFuYWdlZEluc3RhbmNlQ29yZScpLFxuICAgICAgICBdLFxuICAgICAgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMubm9kZVJvbGUgPSBwcm9wcy5ub2RlUm9sZTtcbiAgICB9XG5cblxuICAgIGNvbnN0IGluc3RhbmNlUHJvZmlsZSA9IG5ldyBDZm5JbnN0YW5jZVByb2ZpbGUodGhpcywgJ0luc3RhbmNlUHJvZmlsZScsIHtcbiAgICAgIHJvbGVzOiBbdGhpcy5ub2RlUm9sZS5yb2xlTmFtZV0sXG4gICAgfSk7XG5cbiAgICB0aGlzLmNsdXN0ZXIuYXdzQXV0aC5hZGRSb2xlTWFwcGluZyh0aGlzLm5vZGVSb2xlLCB7XG4gICAgICB1c2VybmFtZTogJ3N5c3RlbTpub2RlOnt7RUMyUHJpdmF0ZUROU05hbWV9fScsXG4gICAgICBncm91cHM6IFtcbiAgICAgICAgJ3N5c3RlbTpib290c3RyYXBwZXJzJyxcbiAgICAgICAgJ3N5c3RlbTpub2RlcycsXG4gICAgICBdLFxuICAgIH0pO1xuXG4gICAgLyoqXG4gICAgICogRm9yIHRoZSBLYXJwZW50ZXIgY29udHJvbGxlciB0byBiZSBhYmxlIHRvIHRhbGsgdG8gdGhlIEFXUyBBUElzLCB3ZSBuZWVkIHRvIHNldCB1cCBhIGZld1xuICAgICAqIHJlc291cmNlcyB3aGljaCB3aWxsIGFsbG93IHRoZSBLYXJwZW50ZXIgY29udHJvbGxlciB0byB1c2UgSUFNIFJvbGVzIGZvciBTZXJ2aWNlIEFjY291bnRzXG4gICAgICovXG5cbiAgICB0aGlzLnNlcnZpY2VBY2NvdW50ID0gdGhpcy5jbHVzdGVyLmFkZFNlcnZpY2VBY2NvdW50KCdrYXJwZW50ZXInLCB7XG4gICAgICBuYW1lOiB0aGlzLnNlcnZpY2VBY2NvdW50TmFtZSxcbiAgICAgIG5hbWVzcGFjZTogdGhpcy5uYW1lc3BhY2UsXG4gICAgfSk7XG5cblxuICAgIC8vIFNldHVwIHRoZSBjb250cm9sbGVyIElBTSBQb2xpY3kgc3RhdGVtZW50c1xuICAgIHRoaXMuYWRkQ29udHJvbGxlclBvbGljeUlBTVBvbGljeVN0YXRlbWVudHMoKTtcblxuICAgIC8vIFNldCByZXBvVXJsIGJhc2VkIG9uIHdoaWNoIHZlcnNpb24gb2YgS2FycGVudGVyIHdlIHdhbnQgdG8gaW5zdGFsbFxuICAgIGNvbnN0IHJlcG9VcmwgPSB0aGlzLmhlbG1SZXBvVVJMRnJvbUthcnBlbnRlclZlcnNpb24oKTtcblxuICAgIC8vIFNldHVwIHRoZSBpbnRlcnJ1cHRpb24gcXVldWUsIGRpZmZlcmVudCBoZWxtIGNoYXJ0IHZlcnNpb25zIGhhdmUgZGlmZmVyZW50IGtleSBuYW1lc1xuICAgIHRoaXMuaW50ZXJydXB0aW9uUXVldWUgPSB0aGlzLmFkZEludGVycnVwdGlvblF1ZXVlKCk7XG5cbiAgICAvLyBNYW5hZ2UgZGlmZmVyZW50IGhlbG0gdmFsdWVzIGRlcGVuZGluZyBvbiBLYXJwZW50ZXIgdmVyc2lvblxuICAgIGlmIChzZW12ZXIuZ3RlKHRoaXMudmVyc2lvbiwgJ3YwLjE5LjAnKSAmJiBzZW12ZXIubHRlKHRoaXMudmVyc2lvbiwgJ3YwLjMyLjAnKSkge1xuICAgICAgdGhpcy5oZWxtQ2hhcnRWYWx1ZXMuc2V0dGluZ3MuYXdzID0ge1xuICAgICAgICBjbHVzdGVyTmFtZTogdGhpcy5jbHVzdGVyLmNsdXN0ZXJOYW1lLFxuICAgICAgICBjbHVzdGVyRW5kcG9pbnQ6IHRoaXMuY2x1c3Rlci5jbHVzdGVyRW5kcG9pbnQsXG4gICAgICAgIGRlZmF1bHRJbnN0YW5jZVByb2ZpbGU6IGluc3RhbmNlUHJvZmlsZS5yZWYsXG4gICAgICAgIGludGVycnVwdGlvblF1ZXVlTmFtZTogdGhpcy5pbnRlcnJ1cHRpb25RdWV1ZT8ucXVldWVOYW1lLFxuICAgICAgfTtcbiAgICB9XG4gICAgaWYgKHNlbXZlci5ndGUodGhpcy52ZXJzaW9uLCAndjAuMzIuMCcpKSB7XG4gICAgICB0aGlzLmhlbG1DaGFydFZhbHVlcy5zZXR0aW5ncyA9IHtcbiAgICAgICAgY2x1c3Rlck5hbWU6IHRoaXMuY2x1c3Rlci5jbHVzdGVyTmFtZSxcbiAgICAgICAgY2x1c3RlckVuZHBvaW50OiB0aGlzLmNsdXN0ZXIuY2x1c3RlckVuZHBvaW50LFxuICAgICAgICBpbnRlcnJ1cHRpb25RdWV1ZTogdGhpcy5pbnRlcnJ1cHRpb25RdWV1ZT8ucXVldWVOYW1lLFxuICAgICAgfTtcbiAgICB9XG5cbiAgICAvLyBUaGVzZSBhcmUgZml4ZWQgdmFsdWVzIHRoYXQgd2Ugc3VwcGx5IHRvIHRoZSBIZWxtIENoYXJ0LlxuICAgIHRoaXMuaGVsbUNoYXJ0VmFsdWVzID0ge1xuICAgICAgLi4ue1xuICAgICAgICBzZXJ2aWNlQWNjb3VudDoge1xuICAgICAgICAgIGNyZWF0ZTogZmFsc2UsXG4gICAgICAgICAgbmFtZTogdGhpcy5zZXJ2aWNlQWNjb3VudC5zZXJ2aWNlQWNjb3VudE5hbWUsXG4gICAgICAgICAgYW5ub3RhdGlvbnM6IHtcbiAgICAgICAgICAgICdla3MuYW1hem9uYXdzLmNvbS9yb2xlLWFybic6IHRoaXMuc2VydmljZUFjY291bnQucm9sZS5yb2xlQXJuLFxuICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgICAgLi4udGhpcy5oZWxtQ2hhcnRWYWx1ZXMsXG4gICAgfTtcblxuICAgIG5ldyBQb2xpY3kodGhpcywgJ0NvbnRyb2xsZXJQb2xpY3knLCB7XG4gICAgICByb2xlczogW3RoaXMuc2VydmljZUFjY291bnQucm9sZV0sXG4gICAgICBzdGF0ZW1lbnRzOiB0aGlzLmNvbnRyb2xsZXJJQU1Qb2xpY3lTdGF0ZW1lbnRzLFxuICAgIH0pO1xuXG4gICAgLyoqXG4gICAgICogRmluYWxseSwgd2UgY2FuIGdvIGFoZWFkIGFuZCBpbnN0YWxsIHRoZSBIZWxtIGNoYXJ0IHByb3ZpZGVkIGZvciBLYXJwZW50ZXIgd2l0aCB0aGUgaW5wdXRzXG4gICAgICogd2UgZGVzaXJlLlxuICAgICAqL1xuICAgIHRoaXMuY2hhcnQgPSB0aGlzLmNsdXN0ZXIuYWRkSGVsbUNoYXJ0KCdrYXJwZW50ZXInLCB7XG4gICAgICAvLyBUaGlzIG9uZSBpcyBpbXBvcnRhbnQsIGlmIHdlIGRvbid0IGFzayBoZWxtIHRvIHdhaXQgZm9yIHJlc291cmNlcyB0byBiZWNvbWUgYXZhaWxhYmxlLCB0aGVcbiAgICAgIC8vIHN1YnNlcXVlbnQgY3JlYXRpb24gb2Yga2FycGVudGVyIHJlc291cmNlcyB3aWxsIGZhaWwuXG4gICAgICB3YWl0OiB0cnVlLFxuICAgICAgY2hhcnQ6ICdrYXJwZW50ZXInLFxuICAgICAgcmVsZWFzZTogJ2thcnBlbnRlcicsXG4gICAgICByZXBvc2l0b3J5OiByZXBvVXJsLFxuICAgICAgbmFtZXNwYWNlOiB0aGlzLm5hbWVzcGFjZSxcbiAgICAgIHZlcnNpb246IHRoaXMudmVyc2lvbixcbiAgICAgIGNyZWF0ZU5hbWVzcGFjZTogZmFsc2UsXG4gICAgICAvLyBXZSB3aWxsIG1lcmdlIG91ciBkeWFubWljIGBoZWxtRXh0cmFWYWx1ZXNgIHdpdGggdGhlIGZpeGVkIHZhbHVlcy4gV2hlcmUgdGhlIGZpeGVkIHZhbHVlc1xuICAgICAgLy8gd2lsbCBvdmVycmlkZSB0aGUgZHluYW1pYyB2YWx1ZXMuXG4gICAgICB2YWx1ZXM6IHsgLi4udGhpcy5oZWxtRXh0cmFWYWx1ZXMsIC4uLnRoaXMuaGVsbUNoYXJ0VmFsdWVzIH0sXG4gICAgfSk7XG5cblxuICAgIC8vIElmIHdlIGFyZSBub3QgaW5zdGFsbGluZyBpdCBpbiB0aGUgYGt1YmUtc3lzdGVtYCBuYW1lc3BhY2U6XG4gICAgLy8gTm90ZTogV2Ugc2hvdWxkIGJlIGluc3RhbGxpbmcgaXQgaW4ga3ViZS1zeXN0ZW0