UNPKG

cdk-eks-karpenter

Version:

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

537 lines 72.7 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.17" }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2Q0FBcUQ7QUFFckQsdURBQThDO0FBQzlDLHVFQUEwRDtBQUMxRCxpREFBeUk7QUFDekksaURBQW9EO0FBQ3BELDJDQUF1QztBQUN2QyxpQ0FBaUM7QUFDakMsbUNBQWdDO0FBd0NoQyxNQUFhLFNBQVUsU0FBUSxzQkFBUztJQWN0QyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXFCO1FBQzdELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDLFNBQVMsSUFBSSxXQUFXLENBQUM7UUFDaEQsSUFBSSxDQUFDLGtCQUFrQixHQUFHLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxXQUFXLENBQUM7UUFDbEUsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksQ0FBQyxlQUFlLEdBQUcsS0FBSyxDQUFDLGVBQWUsSUFBSSxFQUFFLENBQUM7UUFFbkQsSUFBSSxDQUFDLDZCQUE2QixHQUFHLEVBQUUsQ0FBQztRQUN4QyxJQUFJLENBQUMsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO1FBRW5DLElBQUksQ0FBQyxlQUFlLEdBQUc7WUFDckIsUUFBUSxFQUFFO2dCQUNSLEdBQUcsRUFBRSxFQUFFO2FBQ1I7U0FDRixDQUFDO1FBRUY7Ozs7Ozs7Ozs7VUFVRTtRQUNGLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFO1lBQ25CLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxjQUFJLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRTtnQkFDekMsU0FBUyxFQUFFLElBQUksMEJBQWdCLENBQUMsT0FBTyxpQkFBRyxDQUFDLFVB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LEVBQ0YsSUFBSSx5QkFBZSxDQUFDO1lBQ2xCLEdBQUcsRUFBRSxzQ0FBc0M7WUFDM0MsU0FBUyxFQUFFLENBQUMsT0FBTyxTQUFTLFNBQVMsU0FBUyxxQkFBcUIsQ0FBQztZQUNwRSxPQUFPLEVBQUUsQ0FBQyx3QkFBd0IsQ0FBQztZQUNuQyxVQUFVLEVBQUU7Z0JBQ1YsWUFBWSxFQUFFLGdCQUFnQixDQUFDLHdDQUF3QyxFQUFFO29CQUN2RSxDQUFDLHlDQUF5QyxXQUFXLEVBQUUsQ0FBQyxFQUFFLE9BQU87b0JBQ2pFLCtDQUErQyxFQUFFLE1BQU07b0JBQ3ZELENBQUMsd0NBQXdDLFdBQVcsRUFBRSxDQUFDLEVBQUUsT0FBTztvQkFDaEUscUNBQXFDLEVBQUUsV0FBVztvQkFDbEQsOENBQThDLEVBQUUsTUFBTTtpQkFDdkQsQ0FBQztnQkFDRixVQUFVLEVBQUUsZ0JBQWdCLENBQUMsd0NBQXdDLEVBQUU7b0JBQ3JFLGdEQUFnRCxFQUFFLEdBQUc7b0JBQ3JELCtDQUErQyxFQUFFLEdBQUc7aUJBQ3JELENBQUM7YUFDSDtTQUNGLENBQUMsQ0FBQyxDQUFDO0lBQ1IsQ0FBQzs7QUFqbEJILDhCQWtsQkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBd3MsIENmbkpzb24sIER1cmF0aW9uIH0gZnJvbSAnYXdzLWNkay1saWInO1xuaW1wb3J0IHsgQ2x1c3RlciwgSGVsbUNoYXJ0IH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWVrcyc7XG5pbXBvcnQgeyBSdWxlIH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWV2ZW50cyc7XG5pbXBvcnQgeyBTcXNRdWV1ZSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1ldmVudHMtdGFyZ2V0cyc7XG5pbXBvcnQgeyBDZm5JbnN0YW5jZVByb2ZpbGUsIElNYW5hZ2VkUG9saWN5LCBNYW5hZ2VkUG9saWN5LCBQb2xpY3ksIFBvbGljeVN0YXRlbWVudCwgUm9sZSwgU2VydmljZVByaW5jaXBhbCB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1pYW0nO1xuaW1wb3J0IHsgSVF1ZXVlLCBRdWV1ZSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1zcXMnO1xuaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSAnY29uc3RydWN0cyc7XG5pbXBvcnQgKiBhcyBzZW12ZXIgZnJvbSAnc2VtdmVyJztcbmltcG9ydCB7IFV0aWxzIH0gZnJvbSAnLi91dGlscyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgS2FycGVudGVyUHJvcHMge1xuICAvKipcbiAgICogVGhlIEVLUyBDbHVzdGVyIHRvIGF0dGFjaCB0b1xuICAgKi9cbiAgcmVhZG9ubHkgY2x1c3RlcjogQ2x1c3RlcjtcblxuICAvKipcbiAgICogVGhlIEt1YmVybmV0ZXMgbmFtZXNwYWNlIHRvIGluc3RhbGwgdG9cbiAgICpcbiAgICogQGRlZmF1bHQga2FycGVudGVyXG4gICAqL1xuICByZWFkb25seSBuYW1lc3BhY2U/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBLdWJlcm5ldGVzIFNlcnZpY2VBY2NvdW50IG5hbWUgdG8gdXNlXG4gICAqXG4gICAqIEBkZWZhdWx0IGthcnBlbnRlclxuICAgKi9cbiAgcmVhZG9ubHkgc2VydmljZUFjY291bnROYW1lPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgaGVsbSBjaGFydCB2ZXJzaW9uIHRvIGluc3RhbGxcbiAgICpcbiAgICogQGRlZmF1bHQgLSBsYXRlc3RcbiAgICovXG4gIHJlYWRvbmx5IHZlcnNpb246IHN0cmluZztcblxuICAvKipcbiAgICogRXh0cmEgdmFsdWVzIHRvIHBhc3MgdG8gdGhlIEthcnBlbnRlciBIZWxtIGNoYXJ0XG4gICAqL1xuICByZWFkb25seSBoZWxtRXh0cmFWYWx1ZXM/OiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuXG4gIC8qKlxuICAgKiBDdXN0b20gTm9kZVJvbGUgdG8gcGFzcyBmb3IgS2FycGVudGVyIE5vZGVzXG4gICAqL1xuICByZWFkb25seSBub2RlUm9sZT86IFJvbGU7XG59XG5cbmV4cG9ydCBjbGFzcyBLYXJwZW50ZXIgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgY2x1c3RlcjogQ2x1c3RlcjtcbiAgcHVibGljIHJlYWRvbmx5IG5hbWVzcGFjZTogc3RyaW5nO1xuICBwdWJsaWMgcmVhZG9ubHkgc2VydmljZUFjY291bnROYW1lOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSB2ZXJzaW9uOiBzdHJpbmc7XG4gIHB1YmxpYyByZWFkb25seSBub2RlUm9sZTogUm9sZTtcbiAgcHVibGljIHJlYWRvbmx5IGhlbG1FeHRyYVZhbHVlczogYW55O1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgY2hhcnQ6IEhlbG1DaGFydDtcbiAgcHJpdmF0ZSByZWFkb25seSBzZXJ2aWNlQWNjb3VudDogYW55O1xuICBwdWJsaWMgaGVsbUNoYXJ0VmFsdWVzOiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuICBwcml2YXRlIGNvbnRyb2xsZXJJQU1Qb2xpY3lTdGF0ZW1lbnRzOiBQb2xpY3lTdGF0ZW1lbnRbXTtcbiAgcHJpdmF0ZSBpbnRlcnJ1cHRpb25RdWV1ZTogSVF1ZXVlIHwgdW5kZWZpbmVkO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBLYXJwZW50ZXJQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICB0aGlzLmNsdXN0ZXIgPSBwcm9wcy5jbHVzdGVyO1xuICAgIHRoaXMubmFtZXNwYWNlID0gcHJvcHMubmFtZXNwYWNlID8/ICdrYXJwZW50ZXInO1xuICAgIHRoaXMuc2VydmljZUFjY291bnROYW1lID0gcHJvcHMuc2VydmljZUFjY291bnROYW1lID8/ICdrYXJwZW50ZXInO1xuICAgIHRoaXMudmVyc2lvbiA9IHByb3BzLnZlcnNpb247XG4gICAgdGhpcy5oZWxtRXh0cmFWYWx1ZXMgPSBwcm9wcy5oZWxtRXh0cmFWYWx1ZXMgPz8ge307XG5cbiAgICB0aGlzLmNvbnRyb2xsZXJJQU1Qb2xpY3lTdGF0ZW1lbnRzID0gW107XG4gICAgdGhpcy5pbnRlcnJ1cHRpb25RdWV1ZSA9IHVuZGVmaW5lZDtcblxuICAgIHRoaXMuaGVsbUNoYXJ0VmFsdWVzID0ge1xuICAgICAgc2V0dGluZ3M6IHtcbiAgICAgICAgYXdzOiB7fSxcbiAgICAgIH0sXG4gICAgfTtcblxuICAgIC8qXG4gICAgICogV2UgY3JlYXRlIGEgbm9kZSByb2xlIGZvciBLYXJwZW50ZXIgbWFuYWdlZCBub2RlcywgYWxvbmdzaWRlIGFuIGluc3RhbmNlIHByb2ZpbGUgZm9yIHRoZSBFQzJcbiAgICAgKiBpbnN0YW5jZXMgdGhhdCB3aWxsIGJlIG1hbmFnZWQgYnkga2FycGVudGVyLlxuICAgICAqXG4gICAgICogV2Ugd2lsbCBhbHNvIGNyZWF0ZSBhIHJvbGUgbWFwcGluZyBpbiB0aGUgYGF3cy1hdXRoYCBDb25maWdNYXAgc28gdGhhdCB0aGUgbm9kZXMgY2FuIGF1dGhlbnRpY2F0ZVxuICAgICAqIHdpdGggdGhlIEt1YmVybmV0ZXMgQVBJIHVzaW5nIElBTS5cbiAgICAgKlxuICAgICAqIENyZWF0ZSBOb2RlIFJvbGUgaWYgbm9kZVJvbGUgbm90IGFkZGVkIGFzIHByb3BcbiAgICAgKiBNYWtlIHN1cmUgdGhhdCB0aGUgUm9sZSB0aGF0IGlzIGFkZGVkIGRvZXMgbm90IGhhdmUgYW4gSW5zdGFuY2UgUHJvZmlsZSBhc3NvY2lhdGVkIHRvIGl0XG4gICAgICogc2luY2Ugd2Ugd2lsbCBjcmVhdGUgaXQgaGVyZS5cbiAgICAqL1xuICAgIGlmICghcHJvcHMubm9kZVJvbGUpIHtcbiAgICAgIHRoaXMubm9kZVJvbGUgPSBuZXcgUm9sZSh0aGlzLCAnTm9kZVJvbGUnLCB7XG4gICAgICAgIGFzc3VtZWRCeTogbmV3IFNlcnZpY2VQcmluY2lwYWwoYGVjMi4ke0F3cy5VUkxfU1VGRklYfWApLFxuICAgICAgICBtYW5hZ2VkUG9saWNpZXM6IFtcbiAgICAgICAgICBNYW5hZ2VkUG9saWN5LmZyb21Bd3NNYW5hZ2VkUG9saWN5TmFtZSgnQW1hem9uRUtTX0NOSV9Qb2xpY3knKSxcbiAgICAgICAgICBNYW5hZ2VkUG9saWN5LmZyb21Bd3NNYW5hZ2VkUG9saWN5TmFtZSgnQW1hem9uRUtTV29ya2VyTm9kZVBvbGljeScpLFxuICAgICAgICAgIE1hbmFnZWRQb2xpY3kuZnJvbUF3c01hbmFnZWRQb2xpY3lOYW1lKCdBbWF6b25FQzJDb250YWluZXJSZWdpc3RyeVJlYWRPbmx5JyksXG4gICAgICAgICAgTWFuYWdlZFBvbGljeS5mcm9tQXdzTWFuYWdlZFBvbGljeU5hbWUoJ0FtYXpvblNTTU1hbmFnZWRJbnN0YW5jZUNvcmUnKSxcbiAgICAgICAgXSxcbiAgICAgIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLm5vZGVSb2xlID0gcHJvcHMubm9kZVJvbGU7XG4gICAgfVxuXG5cbiAgICBjb25zdCBpbnN0YW5jZVByb2ZpbGUgPSBuZXcgQ2ZuSW5zdGFuY2VQcm9maWxlKHRoaXMsICdJbnN0YW5jZVByb2ZpbGUnLCB7XG4gICAgICByb2xlczogW3RoaXMubm9kZVJvbGUucm9sZU5hbWVdLFxuICAgIH0pO1xuXG4gICAgdGhpcy5jbHVzdGVyLmF3c0F1dGguYWRkUm9sZU1hcHBpbmcodGhpcy5ub2RlUm9sZSwge1xuICAgICAgdXNlcm5hbWU6ICdzeXN0ZW06bm9kZTp7e0VDMlByaXZhdGVETlNOYW1lfX0nLFxuICAgICAgZ3JvdXBzOiBbXG4gICAgICAgICdzeXN0ZW06Ym9vdHN0cmFwcGVycycsXG4gICAgICAgICdzeXN0ZW06bm9kZXMnLFxuICAgICAgXSxcbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIEZvciB0aGUgS2FycGVudGVyIGNvbnRyb2xsZXIgdG8gYmUgYWJsZSB0byB0YWxrIHRvIHRoZSBBV1MgQVBJcywgd2UgbmVlZCB0byBzZXQgdXAgYSBmZXdcbiAgICAgKiByZXNvdXJjZXMgd2hpY2ggd2lsbCBhbGxvdyB0aGUgS2FycGVudGVyIGNvbnRyb2xsZXIgdG8gdXNlIElBTSBSb2xlcyBmb3IgU2VydmljZSBBY2NvdW50c1xuICAgICAqL1xuXG4gICAgdGhpcy5zZXJ2aWNlQWNjb3VudCA9IHRoaXMuY2x1c3Rlci5hZGRTZXJ2aWNlQWNjb3VudCgna2FycGVudGVyJywge1xuICAgICAgbmFtZTogdGhpcy5zZXJ2aWNlQWNjb3VudE5hbWUsXG4gICAgICBuYW1lc3BhY2U6IHRoaXMubmFtZXNwYWNlLFxuICAgIH0pO1xuXG5cbiAgICAvLyBTZXR1cCB0aGUgY29udHJvbGxlciBJQU0gUG9saWN5IHN0YXRlbWVudHNcbiAgICB0aGlzLmFkZENvbnRyb2xsZXJQb2xpY3lJQU1Qb2xpY3lTdGF0ZW1lbnRzKCk7XG5cbiAgICAvLyBTZXQgcmVwb1VybCBiYXNlZCBvbiB3aGljaCB2ZXJzaW9uIG9mIEthcnBlbnRlciB3ZSB3YW50IHRvIGluc3RhbGxcbiAgICBjb25zdCByZXBvVXJsID0gdGhpcy5oZWxtUmVwb1VSTEZyb21LYXJwZW50ZXJWZXJzaW9uKCk7XG5cbiAgICAvLyBTZXR1cCB0aGUgaW50ZXJydXB0aW9uIHF1ZXVlLCBkaWZmZXJlbnQgaGVsbSBjaGFydCB2ZXJzaW9ucyBoYXZlIGRpZmZlcmVudCBrZXkgbmFtZXNcbiAgICB0aGlzLmludGVycnVwdGlvblF1ZXVlID0gdGhpcy5hZGRJbnRlcnJ1cHRpb25RdWV1ZSgpO1xuXG4gICAgLy8gTWFuYWdlIGRpZmZlcmVudCBoZWxtIHZhbHVlcyBkZXBlbmRpbmcgb24gS2FycGVudGVyIHZlcnNpb25cbiAgICBpZiAoc2VtdmVyLmd0ZSh0aGlzLnZlcnNpb24sICd2MC4xOS4wJykgJiYgc2VtdmVyLmx0ZSh0aGlzLnZlcnNpb24sICd2MC4zMi4wJykpIHtcbiAgICAgIHRoaXMuaGVsbUNoYXJ0VmFsdWVzLnNldHRpbmdzLmF3cyA9IHtcbiAgICAgICAgY2x1c3Rlck5hbWU6IHRoaXMuY2x1c3Rlci5jbHVzdGVyTmFtZSxcbiAgICAgICAgY2x1c3RlckVuZHBvaW50OiB0aGlzLmNsdXN0ZXIuY2x1c3RlckVuZHBvaW50LFxuICAgICAgICBkZWZhdWx0SW5zdGFuY2VQcm9maWxlOiBpbnN0YW5jZVByb2ZpbGUucmVmLFxuICAgICAgICBpbnRlcnJ1cHRpb25RdWV1ZU5hbWU6IHRoaXMuaW50ZXJydXB0aW9uUXVldWU/LnF1ZXVlTmFtZSxcbiAgICAgIH07XG4gICAgfVxuICAgIGlmIChzZW12ZXIuZ3RlKHRoaXMudmVyc2lvbiwgJ3YwLjMyLjAnKSkge1xuICAgICAgdGhpcy5oZWxtQ2hhcnRWYWx1ZXMuc2V0dGluZ3MgPSB7XG4gICAgICAgIGNsdXN0ZXJOYW1lOiB0aGlzLmNsdXN0ZXIuY2x1c3Rlck5hbWUsXG4gICAgICAgIGNsdXN0ZXJFbmRwb2ludDogdGhpcy5jbHVzdGVyLmNsdXN0ZXJFbmRwb2ludCxcbiAgICAgICAgaW50ZXJydXB0aW9uUXVldWU6IHRoaXMuaW50ZXJydXB0aW9uUXVldWU/LnF1ZXVlTmFtZSxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gVGhlc2UgYXJlIGZpeGVkIHZhbHVlcyB0aGF0IHdlIHN1cHBseSB0byB0aGUgSGVsbSBDaGFydC5cbiAgICB0aGlzLmhlbG1DaGFydFZhbHVlcyA9IHtcbiAgICAgIC4uLntcbiAgICAgICAgc2VydmljZUFjY291bnQ6IHtcbiAgICAgICAgICBjcmVhdGU6IGZhbHNlLFxuICAgICAgICAgIG5hbWU6IHRoaXMuc2VydmljZUFjY291bnQuc2VydmljZUFjY291bnROYW1lLFxuICAgICAgICAgIGFubm90YXRpb25zOiB7XG4gICAgICAgICAgICAnZWtzLmFtYXpvbmF3cy5jb20vcm9sZS1hcm4nOiB0aGlzLnNlcnZpY2VBY2NvdW50LnJvbGUucm9sZUFybixcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICAgIC4uLnRoaXMuaGVsbUNoYXJ0VmFsdWVzLFxuICAgIH07XG5cbiAgICBuZXcgUG9saWN5KHRoaXMsICdDb250cm9sbGVyUG9saWN5Jywge1xuICAgICAgcm9sZXM6IFt0aGlzLnNlcnZpY2VBY2NvdW50LnJvbGVdLFxuICAgICAgc3RhdGVtZW50czogdGhpcy5jb250cm9sbGVySUFNUG9saWN5U3RhdGVtZW50cyxcbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIEZpbmFsbHksIHdlIGNhbiBnbyBhaGVhZCBhbmQgaW5zdGFsbCB0aGUgSGVsbSBjaGFydCBwcm92aWRlZCBmb3IgS2FycGVudGVyIHdpdGggdGhlIGlucHV0c1xuICAgICAqIHdlIGRlc2lyZS5cbiAgICAgKi9cbiAgICB0aGlzLmNoYXJ0ID0gdGhpcy5jbHVzdGVyLmFkZEhlbG1DaGFydCgna2FycGVudGVyJywge1xuICAgICAgLy8gVGhpcyBvbmUgaXMgaW1wb3J0YW50LCBpZiB3ZSBkb24ndCBhc2sgaGVsbSB0byB3YWl0IGZvciByZXNvdXJjZXMgdG8gYmVjb21lIGF2YWlsYWJsZSwgdGhlXG4gICAgICAvLyBzdWJzZXF1ZW50IGNyZWF0aW9uIG9mIGthcnBlbnRlciByZXNvdXJjZXMgd2lsbCBmYWlsLlxuICAgICAgd2FpdDogdHJ1ZSxcbiAgICAgIGNoYXJ0OiAna2FycGVudGVyJyxcbiAgICAgIHJlbGVhc2U6ICdrYXJwZW50ZXInLFxuICAgICAgcmVwb3NpdG9yeTogcmVwb1VybCxcbiAgICAgIG5hbWVzcGFjZTogdGhpcy5uYW1lc3BhY2UsXG4gICAgICB2ZXJzaW9uOiB0aGlzLnZlcnNpb24sXG4gICAgICBjcmVhdGVOYW1lc3BhY2U6IGZhbHNlLFxuICAgICAgLy8gV2Ugd2lsbCBtZXJnZSBvdXIgZHlhbm1pYyBgaGVsbUV4dHJhVmFsdWVzYCB3aXRoIHRoZSBmaXhlZCB2YWx1ZXMuIFdoZXJlIHRoZSBmaXhlZCB2YWx1ZXNcbiAgICAgIC8vIHdpbGwgb3ZlcnJpZGUgdGhlIGR5bmFtaWMgdmFsdWVzLlxuICAgICAgdmFsdWVzOiB7IC4uLnRoaXMuaGVsbUV4dHJhVmFsdWVzLCAuLi50aGlzLmhlbG1DaGFydFZhbHVlcyB9LFxuICAgIH0pO1xuXG5cbiAgICAvLyBJZiB3ZSBhcmUgbm90IGluc3RhbGxpbmcgaXQgaW4gdGhlIGBrdWJlLXN5c3RlbWAgbmFtZXNwYWNlOlxuICAgIC8vIE5vdGU6IFdlIHNob3VsZCBiZSBpbnN0YWxsaW5nIGl0IGluIGt1YmUtc3lzdGVtLCBwbGVhc2Ugc2VlOiBodHRwczovL2dpdGh1Yi5jb20vYXdzL2thcnBlbnRlci1wcm92aWRlci1hd3MvYmxvYi9mZDJiNjA3NTlmODFkYzBjODY4ODEwY2M0NDQ0MzEwMzA2N2M0ODgwL3dlYnNpdGUvY29udGVudC9lbi92MC4zNi91cGdyYWRpbmc