UNPKG

cdk-eks-karpenter

Version:

CDK construct library that allows you install Karpenter in an AWS EKS cluster

537 lines 72.9 kB
"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