cdk-eks-karpenter
Version:
CDK construct library that allows you install Karpenter in an AWS EKS cluster
537 lines • 72.7 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.17" };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2Q0FBcUQ7QUFFckQsdURBQThDO0FBQzlDLHVFQUEwRDtBQUMxRCxpREFBeUk7QUFDekksaURBQW9EO0FBQ3BELDJDQUF1QztBQUN2QyxpQ0FBaUM7QUFDakMsbUNBQWdDO0FBd0NoQyxNQUFhLFNBQVUsU0FBUSxzQkFBUztJQWN0QyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXFCO1FBQzdELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDLFNBQVMsSUFBSSxXQUFXLENBQUM7UUFDaEQsSUFBSSxDQUFDLGtCQUFrQixHQUFHLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxXQUFXLENBQUM7UUFDbEUsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksQ0FBQyxlQUFlLEdBQUcsS0FBSyxDQUFDLGVBQWUsSUFBSSxFQUFFLENBQUM7UUFFbkQsSUFBSSxDQUFDLDZCQUE2QixHQUFHLEVBQUUsQ0FBQztRQUN4QyxJQUFJLENBQUMsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO1FBRW5DLElBQUksQ0FBQyxlQUFlLEdBQUc7WUFDckIsUUFBUSxFQUFFO2dCQUNSLEdBQUcsRUFBRSxFQUFFO2FBQ1I7U0FDRixDQUFDO1FBRUY7Ozs7Ozs7Ozs7VUFVRTtRQUNGLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFO1lBQ25CLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxjQUFJLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRTtnQkFDekMsU0FBUyxFQUFFLElBQUksMEJBQWdCLENBQUMsT0FBTyxpQkFBRyxDQUFDLFVB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LEVBQ0YsSUFBSSx5QkFBZSxDQUFDO1lBQ2xCLEdBQUcsRUFBRSxzQ0FBc0M7WUFDM0MsU0FBUyxFQUFFLENBQUMsT0FBTyxTQUFTLFNBQVMsU0FBUyxxQkFBcUIsQ0FBQztZQUNwRSxPQUFPLEVBQUUsQ0FBQyx3QkFBd0IsQ0FBQztZQUNuQyxVQUFVLEVBQUU7Z0JBQ1YsWUFBWSxFQUFFLGdCQUFnQixDQUFDLHdDQUF3QyxFQUFFO29CQUN2RSxDQUFDLHlDQUF5QyxXQUFXLEVBQUUsQ0FBQyxFQUFFLE9BQU87b0JBQ2pFLCtDQUErQyxFQUFFLE1BQU07b0JBQ3ZELENBQUMsd0NBQXdDLFdBQVcsRUFBRSxDQUFDLEVBQUUsT0FBTztvQkFDaEUscUNBQXFDLEVBQUUsV0FBVztvQkFDbEQsOENBQThDLEVBQUUsTUFBTTtpQkFDdkQsQ0FBQztnQkFDRixVQUFVLEVBQUUsZ0JBQWdCLENBQUMsd0NBQXdDLEVBQUU7b0JBQ3JFLGdEQUFnRCxFQUFFLEdBQUc7b0JBQ3JELCtDQUErQyxFQUFFLEdBQUc7aUJBQ3JELENBQUM7YUFDSDtTQUNGLENBQUMsQ0FBQyxDQUFDO0lBQ1IsQ0FBQzs7QUFqbEJILDhCQWtsQkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBd3MsIENmbkpzb24sIER1cmF0aW9uIH0gZnJvbSAnYXdzLWNkay1saWInO1xuaW1wb3J0IHsgQ2x1c3RlciwgSGVsbUNoYXJ0IH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWVrcyc7XG5pbXBvcnQgeyBSdWxlIH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWV2ZW50cyc7XG5pbXBvcnQgeyBTcXNRdWV1ZSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1ldmVudHMtdGFyZ2V0cyc7XG5pbXBvcnQgeyBDZm5JbnN0YW5jZVByb2ZpbGUsIElNYW5hZ2VkUG9saWN5LCBNYW5hZ2VkUG9saWN5LCBQb2xpY3ksIFBvbGljeVN0YXRlbWVudCwgUm9sZSwgU2VydmljZVByaW5jaXBhbCB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1pYW0nO1xuaW1wb3J0IHsgSVF1ZXVlLCBRdWV1ZSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1zcXMnO1xuaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSAnY29uc3RydWN0cyc7XG5pbXBvcnQgKiBhcyBzZW12ZXIgZnJvbSAnc2VtdmVyJztcbmltcG9ydCB7IFV0aWxzIH0gZnJvbSAnLi91dGlscyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgS2FycGVudGVyUHJvcHMge1xuICAvKipcbiAgICogVGhlIEVLUyBDbHVzdGVyIHRvIGF0dGFjaCB0b1xuICAgKi9cbiAgcmVhZG9ubHkgY2x1c3RlcjogQ2x1c3RlcjtcblxuICAvKipcbiAgICogVGhlIEt1YmVybmV0ZXMgbmFtZXNwYWNlIHRvIGluc3RhbGwgdG9cbiAgICpcbiAgICogQGRlZmF1bHQga2FycGVudGVyXG4gICAqL1xuICByZWFkb25seSBuYW1lc3BhY2U/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBLdWJlcm5ldGVzIFNlcnZpY2VBY2NvdW50IG5hbWUgdG8gdXNlXG4gICAqXG4gICAqIEBkZWZhdWx0IGthcnBlbnRlclxuICAgKi9cbiAgcmVhZG9ubHkgc2VydmljZUFjY291bnROYW1lPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgaGVsbSBjaGFydCB2ZXJzaW9uIHRvIGluc3RhbGxcbiAgICpcbiAgICogQGRlZmF1bHQgLSBsYXRlc3RcbiAgICovXG4gIHJlYWRvbmx5IHZlcnNpb246IHN0cmluZztcblxuICAvKipcbiAgICogRXh0cmEgdmFsdWVzIHRvIHBhc3MgdG8gdGhlIEthcnBlbnRlciBIZWxtIGNoYXJ0XG4gICAqL1xuICByZWFkb25seSBoZWxtRXh0cmFWYWx1ZXM/OiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuXG4gIC8qKlxuICAgKiBDdXN0b20gTm9kZVJvbGUgdG8gcGFzcyBmb3IgS2FycGVudGVyIE5vZGVzXG4gICAqL1xuICByZWFkb25seSBub2RlUm9sZT86IFJvbGU7XG59XG5cbmV4cG9ydCBjbGFzcyBLYXJwZW50ZXIgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgY2x1c3RlcjogQ2x1c3RlcjtcbiAgcHVibGljIHJlYWRvbmx5IG5hbWVzcGFjZTogc3RyaW5nO1xuICBwdWJsaWMgcmVhZG9ubHkgc2VydmljZUFjY291bnROYW1lOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSB2ZXJzaW9uOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSBub2RlUm9sZTogUm9sZTtcbiAgcHVibGljIHJlYWRvbmx5IGhlbG1FeHRyYVZhbHVlczogYW55O1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgY2hhcnQ6IEhlbG1DaGFydDtcbiAgcHJpdmF0ZSByZWFkb25seSBzZXJ2aWNlQWNjb3VudDogYW55O1xuICBwdWJsaWMgaGVsbUNoYXJ0VmFsdWVzOiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuICBwcml2YXRlIGNvbnRyb2xsZXJJQU1Qb2xpY3lTdGF0ZW1lbnRzOiBQb2xpY3lTdGF0ZW1lbnRbXTtcbiAgcHJpdmF0ZSBpbnRlcnJ1cHRpb25RdWV1ZTogSVF1ZXVlIHwgdW5kZWZpbmVkO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBLYXJwZW50ZXJQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICB0aGlzLmNsdXN0ZXIgPSBwcm9wcy5jbHVzdGVyO1xuICAgIHRoaXMubmFtZXNwYWNlID0gcHJvcHMubmFtZXNwYWNlID8/ICdrYXJwZW50ZXInO1xuICAgIHRoaXMuc2VydmljZUFjY291bnROYW1lID0gcHJvcHMuc2VydmljZUFjY291bnROYW1lID8/ICdrYXJwZW50ZXInO1xuICAgIHRoaXMudmVyc2lvbiA9IHByb3BzLnZlcnNpb247XG4gICAgdGhpcy5oZWxtRXh0cmFWYWx1ZXMgPSBwcm9wcy5oZWxtRXh0cmFWYWx1ZXMgPz8ge307XG5cbiAgICB0aGlzLmNvbnRyb2xsZXJJQU1Qb2xpY3lTdGF0ZW1lbnRzID0gW107XG4gICAgdGhpcy5pbnRlcnJ1cHRpb25RdWV1ZSA9IHVuZGVmaW5lZDtcblxuICAgIHRoaXMuaGVsbUNoYXJ0VmFsdWVzID0ge1xuICAgICAgc2V0dGluZ3M6IHtcbiAgICAgICAgYXdzOiB7fSxcbiAgICAgIH0sXG4gICAgfTtcblxuICAgIC8qXG4gICAgICogV2UgY3JlYXRlIGEgbm9kZSByb2xlIGZvciBLYXJwZW50ZXIgbWFuYWdlZCBub2RlcywgYWxvbmdzaWRlIGFuIGluc3RhbmNlIHByb2ZpbGUgZm9yIHRoZSBFQzJcbiAgICAgKiBpbnN0YW5jZXMgdGhhdCB3aWxsIGJlIG1hbmFnZWQgYnkga2FycGVudGVyLlxuICAgICAqXG4gICAgICogV2Ugd2lsbCBhbHNvIGNyZWF0ZSBhIHJvbGUgbWFwcGluZyBpbiB0aGUgYGF3cy1hdXRoYCBDb25maWdNYXAgc28gdGhhdCB0aGUgbm9kZXMgY2FuIGF1dGhlbnRpY2F0ZVxuICAgICAqIHdpdGggdGhlIEt1YmVybmV0ZXMgQVBJIHVzaW5nIElBTS5cbiAgICAgKlxuICAgICAqIENyZWF0ZSBOb2RlIFJvbGUgaWYgbm9kZVJvbGUgbm90IGFkZGVkIGFzIHByb3BcbiAgICAgKiBNYWtlIHN1cmUgdGhhdCB0aGUgUm9sZSB0aGF0IGlzIGFkZGVkIGRvZXMgbm90IGhhdmUgYW4gSW5zdGFuY2UgUHJvZmlsZSBhc3NvY2lhdGVkIHRvIGl0XG4gICAgICogc2luY2Ugd2Ugd2lsbCBjcmVhdGUgaXQgaGVyZS5cbiAgICAqL1xuICAgIGlmICghcHJvcHMubm9kZVJvbGUpIHtcbiAgICAgIHRoaXMubm9kZVJvbGUgPSBuZXcgUm9sZSh0aGlzLCAnTm9kZVJvbGUnLCB7XG4gICAgICAgIGFzc3VtZWRCeTogbmV3IFNlcnZpY2VQcmluY2lwYWwoYGVjMi4ke0F3cy5VUkxfU1VGRklYfWApLFxuICAgICAgICBtYW5hZ2VkUG9saWNpZXM6IFtcbiAgICAgICAgICBNYW5hZ2VkUG9saWN5LmZyb21Bd3NNYW5hZ2VkUG9saWN5TmFtZSgnQW1hem9uRUtTX0NOSV9Qb2xpY3knKSxcbiAgICAgICAgICBNYW5hZ2VkUG9saWN5LmZyb21Bd3NNYW5hZ2VkUG9saWN5TmFtZSgnQW1hem9uRUtTV29ya2VyTm9kZVBvbGljeScpLFxuICAgICAgICAgIE1hbmFnZWRQb2xpY3kuZnJvbUF3c01hbmFnZWRQb2xpY3lOYW1lKCdBbWF6b25FQzJDb250YWluZXJSZWdpc3RyeVJlYWRPbmx5JyksXG4gICAgICAgICAgTWFuYWdlZFBvbGljeS5mcm9tQXdzTWFuYWdlZFBvbGljeU5hbWUoJ0FtYXpvblNTTU1hbmFnZWRJbnN0YW5jZUNvcmUnKSxcbiAgICAgICAgXSxcbiAgICAgIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLm5vZGVSb2xlID0gcHJvcHMubm9kZVJvbGU7XG4gICAgfVxuXG5cbiAgICBjb25zdCBpbnN0YW5jZVByb2ZpbGUgPSBuZXcgQ2ZuSW5zdGFuY2VQcm9maWxlKHRoaXMsICdJbnN0YW5jZVByb2ZpbGUnLCB7XG4gICAgICByb2xlczogW3RoaXMubm9kZVJvbGUucm9sZU5hbWVdLFxuICAgIH0pO1xuXG4gICAgdGhpcy5jbHVzdGVyLmF3c0F1dGguYWRkUm9sZU1hcHBpbmcodGhpcy5ub2RlUm9sZSwge1xuICAgICAgdXNlcm5hbWU6ICdzeXN0ZW06bm9kZTp7e0VDMlByaXZhdGVETlNOYW1lfX0nLFxuICAgICAgZ3JvdXBzOiBbXG4gICAgICAgICdzeXN0ZW06Ym9vdHN0cmFwcGVycycsXG4gICAgICAgICdzeXN0ZW06bm9kZXMnLFxuICAgICAgXSxcbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIEZvciB0aGUgS2FycGVudGVyIGNvbnRyb2xsZXIgdG8gYmUgYWJsZSB0byB0YWxrIHRvIHRoZSBBV1MgQVBJcywgd2UgbmVlZCB0byBzZXQgdXAgYSBmZXdcbiAgICAgKiByZXNvdXJjZXMgd2hpY2ggd2lsbCBhbGxvdyB0aGUgS2FycGVudGVyIGNvbnRyb2xsZXIgdG8gdXNlIElBTSBSb2xlcyBmb3IgU2VydmljZSBBY2NvdW50c1xuICAgICAqL1xuXG4gICAgdGhpcy5zZXJ2aWNlQWNjb3VudCA9IHRoaXMuY2x1c3Rlci5hZGRTZXJ2aWNlQWNjb3VudCgna2FycGVudGVyJywge1xuICAgICAgbmFtZTogdGhpcy5zZXJ2aWNlQWNjb3VudE5hbWUsXG4gICAgICBuYW1lc3BhY2U6IHRoaXMubmFtZXNwYWNlLFxuICAgIH0pO1xuXG5cbiAgICAvLyBTZXR1cCB0aGUgY29udHJvbGxlciBJQU0gUG9saWN5IHN0YXRlbWVudHNcbiAgICB0aGlzLmFkZENvbnRyb2xsZXJQb2xpY3lJQU1Qb2xpY3lTdGF0ZW1lbnRzKCk7XG5cbiAgICAvLyBTZXQgcmVwb1VybCBiYXNlZCBvbiB3aGljaCB2ZXJzaW9uIG9mIEthcnBlbnRlciB3ZSB3YW50IHRvIGluc3RhbGxcbiAgICBjb25zdCByZXBvVXJsID0gdGhpcy5oZWxtUmVwb1VSTEZyb21LYXJwZW50ZXJWZXJzaW9uKCk7XG5cbiAgICAvLyBTZXR1cCB0aGUgaW50ZXJydXB0aW9uIHF1ZXVlLCBkaWZmZXJlbnQgaGVsbSBjaGFydCB2ZXJzaW9ucyBoYXZlIGRpZmZlcmVudCBrZXkgbmFtZXNcbiAgICB0aGlzLmludGVycnVwdGlvblF1ZXVlID0gdGhpcy5hZGRJbnRlcnJ1cHRpb25RdWV1ZSgpO1xuXG4gICAgLy8gTWFuYWdlIGRpZmZlcmVudCBoZWxtIHZhbHVlcyBkZXBlbmRpbmcgb24gS2FycGVudGVyIHZlcnNpb25cbiAgICBpZiAoc2VtdmVyLmd0ZSh0aGlzLnZlcnNpb24sICd2MC4xOS4wJykgJiYgc2VtdmVyLmx0ZSh0aGlzLnZlcnNpb24sICd2MC4zMi4wJykpIHtcbiAgICAgIHRoaXMuaGVsbUNoYXJ0VmFsdWVzLnNldHRpbmdzLmF3cyA9IHtcbiAgICAgICAgY2x1c3Rlck5hbWU6IHRoaXMuY2x1c3Rlci5jbHVzdGVyTmFtZSxcbiAgICAgICAgY2x1c3RlckVuZHBvaW50OiB0aGlzLmNsdXN0ZXIuY2x1c3RlckVuZHBvaW50LFxuICAgICAgICBkZWZhdWx0SW5zdGFuY2VQcm9maWxlOiBpbnN0YW5jZVByb2ZpbGUucmVmLFxuICAgICAgICBpbnRlcnJ1cHRpb25RdWV1ZU5hbWU6IHRoaXMuaW50ZXJydXB0aW9uUXVldWU/LnF1ZXVlTmFtZSxcbiAgICAgIH07XG4gICAgfVxuICAgIGlmIChzZW12ZXIuZ3RlKHRoaXMudmVyc2lvbiwgJ3YwLjMyLjAnKSkge1xuICAgICAgdGhpcy5oZWxtQ2hhcnRWYWx1ZXMuc2V0dGluZ3MgPSB7XG4gICAgICAgIGNsdXN0ZXJOYW1lOiB0aGlzLmNsdXN0ZXIuY2x1c3Rlck5hbWUsXG4gICAgICAgIGNsdXN0ZXJFbmRwb2ludDogdGhpcy5jbHVzdGVyLmNsdXN0ZXJFbmRwb2ludCxcbiAgICAgICAgaW50ZXJydXB0aW9uUXVldWU6IHRoaXMuaW50ZXJydXB0aW9uUXVldWU/LnF1ZXVlTmFtZSxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gVGhlc2UgYXJlIGZpeGVkIHZhbHVlcyB0aGF0IHdlIHN1cHBseSB0byB0aGUgSGVsbSBDaGFydC5cbiAgICB0aGlzLmhlbG1DaGFydFZhbHVlcyA9IHtcbiAgICAgIC4uLntcbiAgICAgICAgc2VydmljZUFjY291bnQ6IHtcbiAgICAgICAgICBjcmVhdGU6IGZhbHNlLFxuICAgICAgICAgIG5hbWU6IHRoaXMuc2VydmljZUFjY291bnQuc2VydmljZUFjY291bnROYW1lLFxuICAgICAgICAgIGFubm90YXRpb25zOiB7XG4gICAgICAgICAgICAnZWtzLmFtYXpvbmF3cy5jb20vcm9sZS1hcm4nOiB0aGlzLnNlcnZpY2VBY2NvdW50LnJvbGUucm9sZUFybixcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICAgIC4uLnRoaXMuaGVsbUNoYXJ0VmFsdWVzLFxuICAgIH07XG5cbiAgICBuZXcgUG9saWN5KHRoaXMsICdDb250cm9sbGVyUG9saWN5Jywge1xuICAgICAgcm9sZXM6IFt0aGlzLnNlcnZpY2VBY2NvdW50LnJvbGVdLFxuICAgICAgc3RhdGVtZW50czogdGhpcy5jb250cm9sbGVySUFNUG9saWN5U3RhdGVtZW50cyxcbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIEZpbmFsbHksIHdlIGNhbiBnbyBhaGVhZCBhbmQgaW5zdGFsbCB0aGUgSGVsbSBjaGFydCBwcm92aWRlZCBmb3IgS2FycGVudGVyIHdpdGggdGhlIGlucHV0c1xuICAgICAqIHdlIGRlc2lyZS5cbiAgICAgKi9cbiAgICB0aGlzLmNoYXJ0ID0gdGhpcy5jbHVzdGVyLmFkZEhlbG1DaGFydCgna2FycGVudGVyJywge1xuICAgICAgLy8gVGhpcyBvbmUgaXMgaW1wb3J0YW50LCBpZiB3ZSBkb24ndCBhc2sgaGVsbSB0byB3YWl0IGZvciByZXNvdXJjZXMgdG8gYmVjb21lIGF2YWlsYWJsZSwgdGhlXG4gICAgICAvLyBzdWJzZXF1ZW50IGNyZWF0aW9uIG9mIGthcnBlbnRlciByZXNvdXJjZXMgd2lsbCBmYWlsLlxuICAgICAgd2FpdDogdHJ1ZSxcbiAgICAgIGNoYXJ0OiAna2FycGVudGVyJyxcbiAgICAgIHJlbGVhc2U6ICdrYXJwZW50ZXInLFxuICAgICAgcmVwb3NpdG9yeTogcmVwb1VybCxcbiAgICAgIG5hbWVzcGFjZTogdGhpcy5uYW1lc3BhY2UsXG4gICAgICB2ZXJzaW9uOiB0aGlzLnZlcnNpb24sXG4gICAgICBjcmVhdGVOYW1lc3BhY2U6IGZhbHNlLFxuICAgICAgLy8gV2Ugd2lsbCBtZXJnZSBvdXIgZHlhbm1pYyBgaGVsbUV4dHJhVmFsdWVzYCB3aXRoIHRoZSBmaXhlZCB2YWx1ZXMuIFdoZXJlIHRoZSBmaXhlZCB2YWx1ZXNcbiAgICAgIC8vIHdpbGwgb3ZlcnJpZGUgdGhlIGR5bmFtaWMgdmFsdWVzLlxuICAgICAgdmFsdWVzOiB7IC4uLnRoaXMuaGVsbUV4dHJhVmFsdWVzLCAuLi50aGlzLmhlbG1DaGFydFZhbHVlcyB9LFxuICAgIH0pO1xuXG5cbiAgICAvLyBJZiB3ZSBhcmUgbm90IGluc3RhbGxpbmcgaXQgaW4gdGhlIGBrdWJlLXN5c3RlbWAgbmFtZXNwYWNlOlxuICAgIC8vIE5vdGU6IFdlIHNob3VsZCBiZSBpbnN0YWxsaW5nIGl0IGluIGt1YmUtc3lzdGVtLCBwbGVhc2Ugc2VlOiBodHRwczovL2dpdGh1Yi5jb20vYXdzL2thcnBlbnRlci1wcm92aWRlci1hd3MvYmxvYi9mZDJiNjA3NTlmODFkYzBjODY4ODEwY2M0NDQ0MzEwMzA2N2M0ODgwL3dlYnNpdGUvY29udGVudC9lbi92MC4zNi91cGdyYWRpbmc