cdk-serverless-airflow
Version:
[](https://github.com/readybuilderone/serverless-airflow/actions/workflows/build.yml) [
466 lines • 63.3 kB
JavaScript
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Airflow = void 0;
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const path = require("path");
const ec2 = require("@aws-cdk/aws-ec2");
const assets = require("@aws-cdk/aws-ecr-assets");
const ecs = require("@aws-cdk/aws-ecs");
const patterns = require("@aws-cdk/aws-ecs-patterns");
const elasticache = require("@aws-cdk/aws-elasticache");
const iam = require("@aws-cdk/aws-iam");
const logs = require("@aws-cdk/aws-logs");
const rds = require("@aws-cdk/aws-rds");
const s3 = require("@aws-cdk/aws-s3");
const secretsmanager = require("@aws-cdk/aws-secretsmanager");
const servicediscovery = require("@aws-cdk/aws-servicediscovery");
const cdk = require("@aws-cdk/core");
const core_1 = require("@aws-cdk/core");
/**
* @stability stable
*/
class Airflow extends cdk.Construct {
/**
* @stability stable
*/
constructor(scope, id, props = {}) {
var _b, _c;
super(scope, id);
this.fernetKey = (_b = process.env.AIRFLOW__CORE__FERNET_KEY) !== null && _b !== void 0 ? _b : '';
const airflowBucket = this._getAirflowBucket(props);
const vpc = this._getAirflowVPC(props);
//Initial Security Group Property
this.vpcendpointSG = this._createSecurityGroup(vpc, 'vpcendpoint-sg');
this.airflowECSServiceSG = this._createSecurityGroup(vpc, 'airflow-ecsservice-sg');
this.redisSG = this._createSecurityGroup(vpc, 'airflow-redis-sg');
this.databaseSG = this._createSecurityGroup(vpc, 'airflow-database-sg');
this._configSecurityGroup();
//Create VPC Endpoints
this._createVPCEndpoints(vpc);
//Create Database
const airflowDBSecret = this._getAirflowDBSecret();
const dbName = (_c = props.dbName) !== null && _c !== void 0 ? _c : 'airflowdb';
const airflowDB = this._getAirflowDB(vpc, airflowDBSecret, dbName);
//Create Redis
const airflowRedis = this._getAirflowRedis(props, vpc);
//Create AirflowCluster
this._getAirflowECSCluster(props, vpc, airflowBucket, airflowDBSecret, airflowDB, dbName, airflowRedis);
}
/**
* Create Security Group
* @param vpc
* @param securityGroupName
* @returns
*/
_createSecurityGroup(vpc, securityGroupName) {
return new ec2.SecurityGroup(this, securityGroupName, {
vpc,
securityGroupName,
});
}
/**
* Setting rules for security groups
*/
_configSecurityGroup() {
this.airflowECSServiceSG.connections.allowFrom(this.airflowECSServiceSG, ec2.Port.tcp(8080), 'Allow airflow scheduler/worker can connect to webserver');
this.vpcendpointSG.connections.allowFrom(ec2.Peer.ipv4('10.0.0.0/16'), ec2.Port.tcp(443), 'Allow ECS Cluster to access VPC Endpoints');
this.redisSG.connections.allowFrom(this.airflowECSServiceSG, ec2.Port.tcp(6379), 'Allow ECS Cluster to access Redis');
this.databaseSG.connections.allowFrom(this.airflowECSServiceSG, ec2.Port.tcp(5432), 'Allow ECS Cluster to access Database');
}
/**
* Create a S3 bucket for airflow to synch the DAG.
* If the bucket name is provided in the props, it will use
* @param props
* @returns
*/
_getAirflowBucket(props) {
var _b;
const bucketName = (_b = props.bucketName) !== null && _b !== void 0 ? _b : `airflow-bucket-${Math.floor(Math.random() * 1000001)}`;
const airflowBucket = new s3.Bucket(this, 'AirflowBucket', {
bucketName,
removalPolicy: cdk.RemovalPolicy.DESTROY,
blockPublicAccess: new s3.BlockPublicAccess({
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
}),
autoDeleteObjects: true,
});
new core_1.CfnOutput(this, 'airflow-bucket', {
value: airflowBucket.bucketName,
exportName: 'AirflowBucket',
description: 'Buckent Name',
});
return airflowBucket;
}
/**
* Get the VPC for airflow.
* This endpoints will be created for following services:
* - S3
* - ECS
* - CloudWatch
* - Secrets Manager
* @param props
* @returns
*/
_getAirflowVPC(props) {
var _b;
const vpcName = (_b = props.vpcName) !== null && _b !== void 0 ? _b : 'airflow-vpc';
const airflowVPC = new ec2.Vpc(this, vpcName, {
cidr: '10.0.0.0/16',
enableDnsHostnames: true,
enableDnsSupport: true,
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'airflow-public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'airflow-isolated',
subnetType: ec2.SubnetType.ISOLATED,
},
],
});
//TagSubnets
airflowVPC.publicSubnets.forEach(subnet => {
cdk.Tags.of(subnet).add('Name', `public-subnet-${subnet.availabilityZone}-airflow`);
});
airflowVPC.isolatedSubnets.forEach(subnet => {
cdk.Tags.of(subnet).add('Name', `isolated-subnet-${subnet.availabilityZone}-airflow`);
});
return airflowVPC;
}
/**
* Create VPC Endpoints
* @param vpc
*/
_createVPCEndpoints(vpc) {
//Create S3 Gateway VPC Endpoints
vpc.addGatewayEndpoint('s3-endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
subnets: [
{ subnetType: ec2.SubnetType.ISOLATED },
],
});
//Create Interface VPC Endpoints for ECR/ECS/CloudWatch/SecretsManager
vpc.addInterfaceEndpoint('ecr-endpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR,
privateDnsEnabled: true,
securityGroups: [this.vpcendpointSG],
});
vpc.addInterfaceEndpoint('ecr-docker-endpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
privateDnsEnabled: true,
securityGroups: [this.vpcendpointSG],
});
// vpc.addInterfaceEndpoint('ecs-endpoint', {
// service: ec2.InterfaceVpcEndpointAwsService.ECS,
// privateDnsEnabled: true,
// securityGroups: [this.vpcendpointSG],
// });
vpc.addInterfaceEndpoint('cloudwatchlogs-endpoint', {
service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
privateDnsEnabled: true,
securityGroups: [this.vpcendpointSG],
});
vpc.addInterfaceEndpoint('secrets-manager-endpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
privateDnsEnabled: true,
securityGroups: [this.vpcendpointSG],
});
}
_getAirflowDBSecret() {
const databaseSceret = new secretsmanager.Secret(this, 'airflow-db-credentials', {
secretName: 'airflow-db-credentials',
generateSecretString: {
secretStringTemplate: '{"username":"airfflow"}',
generateStringKey: 'password',
passwordLength: 16,
excludeCharacters: '\"@/',
excludePunctuation: true,
},
});
return databaseSceret;
}
/**
* Get Database for Airflow
* @param props
* @param vpc
* @returns
*/
_getAirflowDB(vpc, databaseSceret, dbName) {
const credentials = rds.Credentials.fromSecret(databaseSceret);
const dbInstance = new rds.DatabaseInstance(this, 'airflow-db', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.ISOLATED,
},
engine: rds.DatabaseInstanceEngine.postgres({
version: rds.PostgresEngineVersion.VER_9_6_18,
}),
credentials,
instanceIdentifier: 'airflow-db',
databaseName: dbName,
port: 5432,
instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MICRO),
allocatedStorage: 20,
removalPolicy: cdk.RemovalPolicy.DESTROY,
parameterGroup: rds.ParameterGroup.fromParameterGroupName(this, 'airflow-db-parametergroup', 'default.postgres9.6'),
deletionProtection: false,
securityGroups: [this.databaseSG],
});
return dbInstance;
}
_getAirflowRedis(props, vpc) {
var _b;
const redisName = (_b = props.redisName) !== null && _b !== void 0 ? _b : 'airflowredis';
const redisCluster = new elasticache.CfnCacheCluster(this, 'airflowredis', {
engine: 'redis',
cacheNodeType: 'cache.t2.small',
numCacheNodes: 1,
port: 6379,
clusterName: redisName,
cacheSubnetGroupName: new elasticache.CfnSubnetGroup(this, 'redissubnets', {
description: 'Airflow Redis isolated subnet group',
subnetIds: vpc.isolatedSubnets.map((subnet) => subnet.subnetId),
}).ref,
vpcSecurityGroupIds: [this.redisSG.securityGroupId],
});
return redisCluster;
}
/**
* Create the Ariflow ECS Cluster
* @param props
* @returns
*/
_getAirflowECSCluster(props, vpc, bucket, databaseSceret, database, dbName, redis) {
var _b;
//Create ECS Cluster
const clusterName = (_b = props.ecsclusterName) !== null && _b !== void 0 ? _b : 'AirflowECSCluster';
const airflowCluster = new ecs.Cluster(this, 'airflow-ecs-cluster', {
vpc,
clusterName,
containerInsights: true,
});
//Create Roles
const executionRole = this._createTaskExecutionRole();
const taskRole = this._createTaskRole(bucket);
//Create Log Group
const webserverLogGroup = this._createAirflowLogGroup('airflow-webserver-lg', '/ecs/airflow-webserver');
const schedulerLogGroup = this._createAirflowLogGroup('airflow-scheduler-lg', '/ecs/airflow-scheduler');
const workerLogGroup = this._createAirflowLogGroup('airflow-worker-lg', '/ecs/airflow-worker');
webserverLogGroup.grantWrite(taskRole);
schedulerLogGroup.grantWrite(taskRole);
workerLogGroup.grantWrite(taskRole);
//Create Airflow ECS Service
this._createAirflowWebserverService(executionRole, taskRole, bucket, databaseSceret, database, dbName, airflowCluster, webserverLogGroup);
this._createAirflowSchedulerService(executionRole, taskRole, schedulerLogGroup, bucket, databaseSceret, database, dbName, redis, airflowCluster);
this._createAirflowWorkerService(executionRole, taskRole, workerLogGroup, bucket, databaseSceret, database, dbName, redis, airflowCluster);
return airflowCluster;
}
/**
* Create log group for Airflow ECS Cluster
*/
_createAirflowLogGroup(logGroupId, logGroupName) {
return new logs.LogGroup(this, logGroupId, {
logGroupName,
retention: logs.RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
}
_createTaskExecutionRole() {
const executionRole = new iam.Role(this, 'AirflowTaskExecutionRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
executionRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'));
// executionRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'));
return executionRole;
}
_createTaskRole(bucket) {
const taskRole = new iam.Role(this, 'AirflowTaskRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
// taskRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'));
//S3 Policy
taskRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:ListBucket',
's3:GetObject',
's3:GetBucketLocation',
],
resources: [`${bucket.bucketArn}`, `${bucket.bucketArn}/*`],
}));
//Secrets Manager
taskRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['secretsmanager:GetSecretValue'],
resources: ['*'],
}));
return taskRole;
}
/**
* Create Airflow Webserver ECS Service
*/
_createAirflowWebserverService(executionRole, taskRole, bucket, databaseSceret, database, dbName, airflowCluster, webserverLogGroup) {
const loadBalancedFargateService = new patterns.ApplicationLoadBalancedFargateService(this, 'airflow-webserver-pattners', {
cluster: airflowCluster,
cpu: 512,
memoryLimitMiB: 1024,
taskImageOptions: {
image: ecs.AssetImage.fromDockerImageAsset(this._createAirflowWebServiceDockerImage()),
taskRole,
executionRole,
family: 'airflow-webserver-pattners',
environment: {
AIRFLOW_FERNET_KEY: this.fernetKey,
AIRFLOW_DATABASE_NAME: dbName,
AIRFLOW_DATABASE_PORT_NUMBER: '5432',
AIRFLOW_DATABASE_HOST: database.dbInstanceEndpointAddress,
AIRFLOW_EXECUTOR: 'CeleryExecutor',
AIRFLOW_LOAD_EXAMPLES: 'no',
AIRFLOW__SCHEDULER__DAG_DIR_LIST_INTERVAL: '30',
BUCKET_NAME: bucket.bucketName,
},
secrets: {
AIRFLOW_DATABASE_USERNAME: ecs.Secret.fromSecretsManager(databaseSceret, 'username'),
AIRFLOW_DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(databaseSceret, 'password'),
},
containerPort: 8080,
logDriver: ecs.LogDriver.awsLogs({
streamPrefix: 'ecs',
logGroup: webserverLogGroup,
}),
},
securityGroups: [this.airflowECSServiceSG],
serviceName: 'AirflowWebserverServiceName',
desiredCount: 1,
loadBalancerName: 'Airflow-Webserver-LB',
cloudMapOptions: {
name: 'webserver',
dnsRecordType: servicediscovery.DnsRecordType.A,
dnsTtl: cdk.Duration.seconds(30),
cloudMapNamespace: new servicediscovery.PrivateDnsNamespace(this, 'webserver-dns-namespace', {
name: 'airflow',
vpc: airflowCluster.vpc,
}),
},
});
loadBalancedFargateService.targetGroup.configureHealthCheck({
path: '/health',
interval: cdk.Duration.seconds(60),
timeout: cdk.Duration.seconds(20),
});
}
/**
* Create Airflow Scheduler ECS Service
*/
_createAirflowSchedulerService(executionRole, taskRole, schedulerLogGroup, bucket, databaseSceret, database, dbName, redis, airflowCluster) {
//Create Task Definition
const schedulerTask = new ecs.FargateTaskDefinition(this, 'AriflowSchedulerTask', {
executionRole,
taskRole,
cpu: 512,
memoryLimitMiB: 2048,
family: 'airflow-scheduler',
});
schedulerTask.addContainer('airflow-scheduler-container', {
image: ecs.AssetImage.fromDockerImageAsset(this._createAirflowSchedulerDockerImage()),
logging: new ecs.AwsLogDriver({
streamPrefix: 'ecs',
logGroup: schedulerLogGroup,
}),
environment: {
AIRFLOW_FERNET_KEY: this.fernetKey,
AIRFLOW_DATABASE_NAME: dbName,
AIRFLOW_DATABASE_PORT_NUMBER: '5432',
AIRFLOW_DATABASE_HOST: database.dbInstanceEndpointAddress,
AIRFLOW_EXECUTOR: 'CeleryExecutor',
AIRFLOW_WEBSERVER_HOST: 'webserver.airflow',
AIRFLOW_LOAD_EXAMPLES: 'no',
AIRFLOW__SCHEDULER__DAG_DIR_LIST_INTERVAL: '30',
REDIS_HOST: redis.attrRedisEndpointAddress,
BUCKET_NAME: bucket.bucketName,
},
secrets: {
AIRFLOW_DATABASE_USERNAME: ecs.Secret.fromSecretsManager(databaseSceret, 'username'),
AIRFLOW_DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(databaseSceret, 'password'),
},
});
//Create AirflowSchedulerService
new ecs.FargateService(this, 'AirflowSchedulerService', {
cluster: airflowCluster,
taskDefinition: schedulerTask,
serviceName: 'AirflowSchedulerServiceName',
securityGroups: [this.airflowECSServiceSG],
});
}
/**
* Create Airflow Worker ECS Service
*/
_createAirflowWorkerService(executionRole, taskRole, workerLogGroup, bucket, databaseSceret, database, dbName, redis, airflowCluster) {
//Create Task Definition
const workerTask = new ecs.FargateTaskDefinition(this, 'AriflowworkerTask', {
executionRole,
taskRole,
cpu: 1024,
memoryLimitMiB: 3072,
family: 'airflow-worker',
});
workerTask.addContainer('airflow-worker-container', {
image: ecs.AssetImage.fromDockerImageAsset(this._createAirflowWorkerDockerImage()),
logging: new ecs.AwsLogDriver({
streamPrefix: 'ecs',
logGroup: workerLogGroup,
}),
environment: {
AIRFLOW_FERNET_KEY: this.fernetKey,
AIRFLOW_DATABASE_NAME: dbName,
AIRFLOW_DATABASE_PORT_NUMBER: '5432',
AIRFLOW_DATABASE_HOST: database.dbInstanceEndpointAddress,
AIRFLOW_EXECUTOR: 'CeleryExecutor',
AIRFLOW_WEBSERVER_HOST: 'webserver.airflow',
AIRFLOW_LOAD_EXAMPLES: 'no',
AIRFLOW__SCHEDULER__DAG_DIR_LIST_INTERVAL: '30',
REDIS_HOST: redis.attrRedisEndpointAddress,
BUCKET_NAME: bucket.bucketName,
},
secrets: {
AIRFLOW_DATABASE_USERNAME: ecs.Secret.fromSecretsManager(databaseSceret, 'username'),
AIRFLOW_DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(databaseSceret, 'password'),
},
portMappings: [{ containerPort: 8793 }],
});
//Create AirflowWorkerService
new ecs.FargateService(this, 'AirflowWorkerService', {
cluster: airflowCluster,
taskDefinition: workerTask,
serviceName: 'AirflowWorkerServiceName',
securityGroups: [this.airflowECSServiceSG],
});
}
_createAirflowWebServiceDockerImage() {
return new assets.DockerImageAsset(this, 'airflow-webserver', {
directory: path.join(__dirname, '/../docker-images/airflow-webserver'),
});
}
_createAirflowSchedulerDockerImage() {
return new assets.DockerImageAsset(this, 'airflow-scheduler', {
directory: path.join(__dirname, '/../docker-images/airflow-scheduler'),
});
}
_createAirflowWorkerDockerImage() {
return new assets.DockerImageAsset(this, 'airflow-worker', {
directory: path.join(__dirname, '/../docker-images/airflow-worker'),
});
}
}
exports.Airflow = Airflow;
_a = JSII_RTTI_SYMBOL_1;
Airflow[_a] = { fqn: "cdk-serverless-airflow.Airflow", version: "0.7.6" };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2QkFBNkI7QUFDN0Isd0NBQXdDO0FBQ3hDLGtEQUFrRDtBQUNsRCx3Q0FBd0M7QUFDeEMsc0RBQXNEO0FBQ3RELHdEQUF3RDtBQUN4RCx3Q0FBd0M7QUFDeEMsMENBQTBDO0FBQzFDLHdDQUF3QztBQUN4QyxzQ0FBc0M7QUFDdEMsOERBQThEO0FBQzlELGtFQUFrRTtBQUNsRSxxQ0FBcUM7QUFDckMsd0NBQTBDOzs7O0FBVzFDLE1BQWEsT0FBUSxTQUFRLEdBQUcsQ0FBQyxTQUFTOzs7O0lBT3hDLFlBQVksS0FBb0IsRUFBRSxFQUFVLEVBQUUsUUFBc0IsRUFBRTs7UUFDcEUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixJQUFJLENBQUMsU0FBUyxTQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMseUJBQXlCLG1DQUFHLEVBQUUsQ0FBQztRQUU1RCxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDcEQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUV2QyxpQ0FBaUM7UUFDakMsSUFBSSxDQUFDLGFBQWEsR0FBRyxJQUFJLENBQUMsb0JBQW9CLENBQUMsR0FBRyxFQUFFLGdCQUFnQixDQUFDLENBQUM7UUFDdEUsSUFBSSxDQUFDLG1CQUFtQixHQUFHLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxHQUFHLEVBQUUsdUJBQXVCLENBQUMsQ0FBQztRQUNuRixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxHQUFHLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxHQUFHLEVBQUUscUJBQXFC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RTtZQUM1RCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUscUNBQXFDLENBQUM7U0FDdkUsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVPLGtDQUFrQztRQUN4QyxPQUFPLElBQUksTUFBTSxDQUFDLGdCQUFnQixDQUFDLElBQUksRUFBRSxtQkFBbUIsRUFBRTtZQUM1RCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUscUNBQXFDLENBQUM7U0FDdkUsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVPLCtCQUErQjtRQUNyQyxPQUFPLElBQUksTUFBTSxDQUFDLGdCQUFnQixDQUFDLElBQUksRUFBRSxnQkFBZ0IsRUFBRTtZQUN6RCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsa0NBQWtDLENBQUM7U0FDcEUsQ0FBQyxDQUFDO0lBQ0wsQ0FBQzs7QUF2ZUgsMEJBd2VDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCAqIGFzIGVjMiBmcm9tICdAYXdzLWNkay9hd3MtZWMyJztcbmltcG9ydCAqIGFzIGFzc2V0cyBmcm9tICdAYXdzLWNkay9hd3MtZWNyLWFzc2V0cyc7XG5pbXBvcnQgKiBhcyBlY3MgZnJvbSAnQGF3cy1jZGsvYXdzLWVjcyc7XG5pbXBvcnQgKiBhcyBwYXR0ZXJucyBmcm9tICdAYXdzLWNkay9hd3MtZWNzLXBhdHRlcm5zJztcbmltcG9ydCAqIGFzIGVsYXN0aWNhY2hlIGZyb20gJ0Bhd3MtY2RrL2F3cy1lbGFzdGljYWNoZSc7XG5pbXBvcnQgKiBhcyBpYW0gZnJvbSAnQGF3cy1jZGsvYXdzLWlhbSc7XG5pbXBvcnQgKiBhcyBsb2dzIGZyb20gJ0Bhd3MtY2RrL2F3cy1sb2dzJztcbmltcG9ydCAqIGFzIHJkcyBmcm9tICdAYXdzLWNkay9hd3MtcmRzJztcbmltcG9ydCAqIGFzIHMzIGZyb20gJ0Bhd3MtY2RrL2F3cy1zMyc7XG5pbXBvcnQgKiBhcyBzZWNyZXRzbWFuYWdlciBmcm9tICdAYXdzLWNkay9hd3Mtc2VjcmV0c21hbmFnZXInO1xuaW1wb3J0ICogYXMgc2VydmljZWRpc2NvdmVyeSBmcm9tICdAYXdzLWNkay9hd3Mtc2VydmljZWRpc2NvdmVyeSc7XG5pbXBvcnQgKiBhcyBjZGsgZnJvbSAnQGF3cy1jZGsvY29yZSc7XG5pbXBvcnQgeyBDZm5PdXRwdXQgfSBmcm9tICdAYXdzLWNkay9jb3JlJztcblxuZXhwb3J0IGludGVyZmFjZSBBaXJmbG93UHJvcHMge1xuICByZWFkb25seSBidWNrZXROYW1lPzogc3RyaW5nO1xuICByZWFkb25seSB2cGNOYW1lPzogc3RyaW5nO1xuICByZWFkb25seSBkYk5hbWU/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHJlZGlzTmFtZT86IHN0cmluZztcbiAgcmVhZG9ubHkgZWNzY2x1c3Rlck5hbWU/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGFpcmZsb3dGZXJuZXRLZXk/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjbGFzcyBBaXJmbG93IGV4dGVuZHMgY2RrLkNvbnN0cnVjdCB7XG4gIHByaXZhdGUgcmVhZG9ubHkgZmVybmV0S2V5OiBzdHJpbmc7XG4gIHByaXZhdGUgcmVhZG9ubHkgYWlyZmxvd0VDU1NlcnZpY2VTRzogZWMyLklTZWN1cml0eUdyb3VwO1xuICBwcml2YXRlIHJlYWRvbmx5IHZwY2VuZHBvaW50U0c6IGVjMi5JU2VjdXJpdHlHcm91cDtcbiAgcHJpdmF0ZSByZWFkb25seSByZWRpc1NHOiBlYzIuSVNlY3VyaXR5R3JvdXA7XG4gIHByaXZhdGUgcmVhZG9ubHkgZGF0YWJhc2VTRzogZWMyLklTZWN1cml0eUdyb3VwO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBjZGsuQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogQWlyZmxvd1Byb3BzID0ge30pIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgdGhpcy5mZXJuZXRLZXkgPSBwcm9jZXNzLmVudi5BSVJGTE9XX19DT1JFX19GRVJORVRfS0VZPz8gJyc7XG5cbiAgICBjb25zdCBhaXJmbG93QnVja2V0ID0gdGhpcy5fZ2V0QWlyZmxvd0J1Y2tldChwcm9wcyk7XG4gICAgY29uc3QgdnBjID0gdGhpcy5fZ2V0QWlyZmxvd1ZQQyhwcm9wcyk7XG5cbiAgICAvL0luaXRpYWwgU2VjdXJpdHkgR3JvdXAgUHJvcGVydHlcbiAgICB0aGlzLnZwY2VuZHBvaW50U0cgPSB0aGlzLl9jcmVhdGVTZWN1cml0eUdyb3VwKHZwYywgJ3ZwY2VuZHBvaW50LXNnJyk7XG4gICAgdGhpcy5haXJmbG93RUNTU2VydmljZVNHID0gdGhpcy5fY3JlYXRlU2VjdXJpdHlHcm91cCh2cGMsICdhaXJmbG93LWVjc3NlcnZpY2Utc2cnKTtcbiAgICB0aGlzLnJlZGlzU0cgPSB0aGlzLl9jcmVhdGVTZWN1cml0eUdyb3VwKHZwYywgJ2FpcmZsb3ctcmVkaXMtc2cnKTtcbiAgICB0aGlzLmRhdGFiYXNlU0cgPSB0aGlzLl9jcmVhdGVTZWN1cml0eUdyb3VwKHZwYywgJ2FpcmZsb3ctZGF0YWJhc2Utc2cnKTtcbiAgICB0aGlzLl9jb25maWdTZWN1cml0eUdyb3VwKCk7XG5cbiAgICAvL0NyZWF0ZSBWUEMgRW5kcG9pbnRzXG4gICAgdGhpcy5fY3JlYXRlVlBDRW5kcG9pbnRzKHZwYyk7XG5cbiAgICAvL0NyZWF0ZSBEYXRhYmFzZVxuICAgIGNvbnN0IGFpcmZsb3dEQlNlY3JldCA9IHRoaXMuX2dldEFpcmZsb3dEQlNlY3JldCgpO1xuICAgIGNvbnN0IGRiTmFtZSA9IHByb3BzLmRiTmFtZSA/PyAnYWlyZmxvd2RiJztcbiAgICBjb25zdCBhaXJmbG93REIgPSB0aGlzLl9nZXRBaXJmbG93REIodnBjLCBhaXJmbG93REJTZWNyZXQsIGRiTmFtZSk7XG5cbiAgICAvL0NyZWF0ZSBSZWRpc1xuICAgIGNvbnN0IGFpcmZsb3dSZWRpcyA9IHRoaXMuX2dldEFpcmZsb3dSZWRpcyhwcm9wcywgdnBjKTtcblxuICAgIC8vQ3JlYXRlIEFpcmZsb3dDbHVzdGVyXG4gICAgdGhpcy5fZ2V0QWlyZmxvd0VDU0NsdXN0ZXIocHJvcHMsIHZwYywgYWlyZmxvd0J1Y2tldCwgYWlyZmxvd0RCU2VjcmV0LCBhaXJmbG93REIsIGRiTmFtZSwgYWlyZmxvd1JlZGlzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgU2VjdXJpdHkgR3JvdXBcbiAgICogQHBhcmFtIHZwY1xuICAgKiBAcGFyYW0gc2VjdXJpdHlHcm91cE5hbWVcbiAgICogQHJldHVybnNcbiAgICovXG4gIHByaXZhdGUgX2NyZWF0ZVNlY3VyaXR5R3JvdXAodnBjOiBlYzIuSVZwYywgc2VjdXJpdHlHcm91cE5hbWU6IHN0cmluZyk6IGVjMi5JU2VjdXJpdHlHcm91cCB7XG4gICAgcmV0dXJuIG5ldyBlYzIuU2VjdXJpdHlHcm91cCh0aGlzLCBzZWN1cml0eUdyb3VwTmFtZSwge1xuICAgICAgdnBjLFxuICAgICAgc2VjdXJpdHlHcm91cE5hbWUsXG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogU2V0dGluZyBydWxlcyBmb3Igc2VjdXJpdHkgZ3JvdXBzXG4gICAqL1xuICBwcml2YXRlIF9jb25maWdTZWN1cml0eUdyb3VwKCkge1xuICAgIHRoaXMuYWlyZmxvd0VDU1NlcnZpY2VTRy5jb25uZWN0aW9ucy5hbGxvd0Zyb20odGhpcy5haXJmbG93RUNTU2VydmljZVNHLCBlYzIuUG9ydC50Y3AoODA4MCksICdBbGxvdyBhaXJmbG93IHNjaGVkdWxlci93b3JrZXIgY2FuIGNvbm5lY3QgdG8gd2Vic2VydmVyJyk7XG4gICAgdGhpcy52cGNlbmRwb2ludFNHLmNvbm5lY3Rpb25zLmFsbG93RnJvbShlYzIuUGVlci5pcHY0KCcxMC4wLjAuMC8xNicpLCBlYzIuUG9ydC50Y3AoNDQzKSwgJ0FsbG93IEVDUyBDbHVzdGVyIHRvIGFjY2VzcyBWUEMgRW5kcG9pbnRzJyk7XG4gICAgdGhpcy5yZWRpc1NHLmNvbm5lY3Rpb25zLmFsbG93RnJvbSh0aGlzLmFpcmZsb3dFQ1NTZXJ2aWNlU0csIGVjMi5Qb3J0LnRjcCg2Mzc5KSwgJ0FsbG93IEVDUyBDbHVzdGVyIHRvIGFjY2VzcyBSZWRpcycpO1xuICAgIHRoaXMuZGF0YWJhc2VTRy5jb25uZWN0aW9ucy5hbGxvd0Zyb20odGhpcy5haXJmbG93RUNTU2VydmljZVNHLCBlYzIuUG9ydC50Y3AoNTQzMiksICdBbGxvdyBFQ1MgQ2x1c3RlciB0byBhY2Nlc3MgRGF0YWJhc2UnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBTMyBidWNrZXQgZm9yIGFpcmZsb3cgdG8gc3luY2ggdGhlIERBRy5cbiAgICogSWYgdGhlIGJ1Y2tldCBuYW1lIGlzIHByb3ZpZGVkIGluIHRoZSBwcm9wcywgaXQgd2lsbCB1c2VcbiAgICogQHBhcmFtIHByb3BzXG4gICAqIEByZXR1cm5zXG4gICAqL1xuICBwcml2YXRlIF9nZXRBaXJmbG93QnVja2V0KHByb3BzOiBBaXJmbG93UHJvcHMpOiBzMy5JQnVja2V0IHtcbiAgICBjb25zdCBidWNrZXROYW1lID0gcHJvcHMuYnVja2V0TmFtZSA/PyBgYWlyZmxvdy1idWNrZXQtJHtNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiAxMDAwMDAxKX1gO1xuICAgIGNvbnN0IGFpcmZsb3dCdWNrZXQgPSBuZXcgczMuQnVja2V0KHRoaXMsICdBaXJmbG93QnVja2V0Jywge1xuICAgICAgYnVja2V0TmFtZSxcbiAgICAgIHJlbW92YWxQb2xpY3k6IGNkay5SZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICBibG9ja1B1YmxpY0FjY2VzczogbmV3IHMzLkJsb2NrUHVibGljQWNjZXNzKHtcbiAgICAgICAgYmxvY2tQdWJsaWNBY2xzOiB0cnVlLFxuICAgICAgICBibG9ja1B1YmxpY1BvbGljeTogdHJ1ZSxcbiAgICAgICAgaWdub3JlUHVibGljQWNsczogdHJ1ZSxcbiAgICAgICAgcmVzdHJpY3RQdWJsaWNCdWNrZXRzOiB0cnVlLFxuICAgICAgfSksXG4gICAgICBhdXRvRGVsZXRlT2JqZWN0czogdHJ1ZSxcbiAgICB9KTtcbiAgICBuZXcgQ2ZuT3V0cHV0KHRoaXMsICdhaXJmbG93LWJ1Y2tldCcsIHtcbiAgICAgIHZhbHVlOiBhaXJmbG93QnVja2V0LmJ1Y2tldE5hbWUsXG4gICAgICBleHBvcnROYW1lOiAnQWlyZmxvd0J1Y2tldCcsXG4gICAgICBkZXNjcmlwdGlvbjogJ0J1Y2tlbnQgTmFtZScsXG4gICAgfSk7XG4gICAgcmV0dXJuIGFpcmZsb3dCdWNrZXQ7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBWUEMgZm9yIGFpcmZsb3cuXG4gICAqIFRoaXMgZW5kcG9pbnRzIHdpbGwgYmUgY3JlYXRlZCBmb3IgZm9sbG93aW5nIHNlcnZpY2VzOlxuICAgKiAgIC0gUzNcbiAgICogICAtIEVDU1xuICAgKiAgIC0gQ2xvdWRXYXRjaFxuICAgKiAgIC0gU2VjcmV0cyBNYW5hZ2VyXG4gICAqIEBwYXJhbSBwcm9wc1xuICAgKiBAcmV0dXJuc1xuICAgKi9cbiAgcHJpdmF0ZSBfZ2V0QWlyZmxvd1ZQQyhwcm9wczogQWlyZmxvd1Byb3BzKTogZWMyLklWcGMge1xuICAgIGNvbnN0IHZwY05hbWUgPSBwcm9wcy52cGNOYW1lID8/ICdhaXJmbG93LXZwYyc7XG4gICAgY29uc3QgYWlyZmxvd1ZQQyA9IG5ldyBlYzIuVnBjKHRoaXMsIHZwY05hbWUsIHtcbiAgICAgIGNpZHI6ICcxMC4wLjAuMC8xNicsXG4gICAgICBlbmFibGVEbnNIb3N0bmFtZXM6IHRydWUsXG4gICAgICBlbmFibGVEbnNTdXBwb3J0OiB0cnVlLFxuICAgICAgbWF4QXpzOiAyLFxuICAgICAgc3VibmV0Q29uZmlndXJhdGlvbjogW1xuICAgICAgICB7XG4gICAgICAgICAgY2lkck1hc2s6IDI0LFxuICAgICAgICAgIG5hbWU6ICdhaXJmbG93LXB1YmxpYycsXG4gICAgICAgICAgc3VibmV0VHlwZTogZWMyLlN1Ym5ldFR5cGUuUFVCTElDLFxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgY2lkck1hc2s6IDI0LFxuICAgICAgICAgIG5hbWU6ICdhaXJmbG93LWlzb2xhdGVkJyxcbiAgICAgICAgICBzdWJuZXRUeXBlOiBlYzIuU3VibmV0VHlwZS5JU09MQVRFRCxcbiAgICAgICAgfSxcbiAgICAgIF0sXG4gICAgfSk7XG5cbiAgICAvL1RhZ1N1Ym5ldHNcbiAgICBhaXJmbG93VlBDLnB1YmxpY1N1Ym5ldHMuZm9yRWFjaChzdWJuZXQgPT4ge1xuICAgICAgY2RrLlRhZ3Mub2Yoc3VibmV0KS5hZGQoJ05hbWUnLCBgcHVibGljLXN1Ym5ldC0ke3N1Ym5ldC5hdmFpbGFiaWxpdHlab25lfS1haXJmbG93YCk7XG4gICAgfSk7XG4gICAgYWlyZmxvd1ZQQy5pc29sYXRlZFN1Ym5ldHMuZm9yRWFjaChzdWJuZXQgPT4ge1xuICAgICAgY2RrLlRhZ3Mub2Yoc3VibmV0KS5hZGQoJ05hbWUnLCBgaXNvbGF0ZWQtc3VibmV0LSR7c3VibmV0LmF2YWlsYWJpbGl0eVpvbmV9LWFpcmZsb3dgKTtcbiAgICB9KTtcblxuICAgIHJldHVybiBhaXJmbG93VlBDO1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBWUEMgRW5kcG9pbnRzXG4gICAqIEBwYXJhbSB2cGNcbiAgICovXG4gIHByaXZhdGUgX2NyZWF0ZVZQQ0VuZHBvaW50cyh2cGM6IGVjMi5JVnBjKSB7XG4gICAgLy9DcmVhdGUgUzMgR2F0ZXdheSBWUEMgRW5kcG9pbnRzXG4gICAgdnBjLmFkZEdhdGV3YXlFbmRwb2ludCgnczMtZW5kcG9pbnQnLCB7XG4gICAgICBzZXJ2aWNlOiBlYzIuR2F0ZXdheVZwY0VuZHBvaW50QXdzU2VydmljZS5TMyxcbiAgICAgIHN1Ym5ldHM6IFtcbiAgICAgICAgeyBzdWJuZXRUeXBlOiBlYzIuU3VibmV0VHlwZS5JU09MQVRFRCB9LFxuICAgICAgXSxcbiAgICB9KTtcblxuICAgIC8vQ3JlYXRlIEludGVyZmFjZSBWUEMgRW5kcG9pbnRzIGZvciBFQ1IvRUNTL0Nsb3VkV2F0Y2gvU2VjcmV0c01hbmFnZXJcbiAgICB2cGMuYWRkSW50ZXJmYWNlRW5kcG9pbnQoJ2Vjci1lbmRwb2ludCcsIHtcbiAgICAgIHNlcnZpY2U6IGVjMi5JbnRlcmZhY2VWcGNFbmRwb2ludEF3c1NlcnZpY2UuRUNSLFxuICAgICAgcHJpdmF0ZURuc0VuYWJsZWQ6IHRydWUsXG4gICAgICBzZWN1cml0eUdyb3VwczogW3RoaXMudnBjZW5kcG9pbnRTR10sXG4gICAgfSk7XG4gICAgdnBjLmFkZEludGVyZmFjZUVuZHBvaW50KCdlY3ItZG9ja2VyLWVuZHBvaW50Jywge1xuICAgICAgc2VydmljZTogZWMyLkludGVyZmFjZVZwY0VuZHBvaW50QXdzU2VydmljZS5FQ1JfRE9DS0VSLFxuICAgICAgcHJpdmF0ZURuc0VuYWJsZWQ6IHRydWUsXG4gICAgICBzZWN1cml0eUdyb3VwczogW3RoaXMudnBjZW5kcG9pbnRTR10sXG4gICAgfSk7XG4gICAgLy8gdnBjLmFkZEludGVyZmFjZUVuZHBvaW50KCdlY3MtZW5kcG9pbnQnLCB7XG4gICAgLy8gICBzZXJ2aWNlOiBlYzIuSW50ZXJmYWNlVnBjRW5kcG9pbnRBd3NTZXJ2aWNlLkVDUyxcbiAgICAvLyAgIHByaXZhdGVEbnNFbmFibGVkOiB0cnVlLFxuICAgIC8vICAgc2VjdXJpdHlHcm91cHM6IFt0aGlzLnZwY2VuZHBvaW50U0ddLFxuICAgIC8vIH0pO1xuICAgIHZwYy5hZGRJbnRlcmZhY2VFbmRwb2ludCgnY2xvdWR3YXRjaGxvZ3MtZW5kcG9pbnQnLCB7XG4gICAgICBzZXJ2aWNlOiBlYzIuSW50ZXJmYWNlVnBjRW5kcG9pbnRBd3NTZXJ2aWNlLkNMT1VEV0FUQ0hfTE9HUyxcbiAgICAgIHByaXZhdGVEbnNFbmFibGVkOiB0cnVlLFxuICAgICAgc2VjdXJpdHlHcm91cHM6IFt0aGlzLnZwY2VuZHBvaW50U0ddLFxuICAgIH0pO1xuICAgIHZwYy5hZGRJbnRlcmZhY2VFbmRwb2ludCgnc2VjcmV0cy1tYW5hZ2VyLWVuZHBvaW50Jywge1xuICAgICAgc2VydmljZTogZWMyLkludGVyZmFjZVZwY0VuZHBvaW50QXdzU2VydmljZS5TRUNSRVRTX01BTkFHRVIsXG4gICAgICBwcml2YXRlRG5zRW5hYmxlZDogdHJ1ZSxcbiAgICAgIHNlY3VyaXR5R3JvdXBzOiBbdGhpcy52cGNlbmRwb2ludFNHXSxcbiAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgX2dldEFpcmZsb3dEQlNlY3JldCgpOiBzZWNyZXRzbWFuYWdlci5TZWNyZXQge1xuICAgIGNvbnN0IGRhdGFiYXNlU2NlcmV0ID0gbmV3IHNlY3JldHNtYW5hZ2VyLlNlY3JldCh0aGlzLCAnYWlyZmxvdy1kYi1jcmVkZW50aWFscycsIHtcbiAgICAgIHNlY3JldE5hbWU6ICdhaXJmbG93LWRiLWNyZWRlbnRpYWxzJyxcbiAgICAgIGdlbmVyYXRlU2VjcmV0U3RyaW5nOiB7XG4gICAgICAgIHNlY3JldFN0cmluZ1RlbXBsYXRlOiAne1widXNlcm5hbWVcIjpcImFpcmZmbG93XCJ9JyxcbiAgICAgICAgZ2VuZXJhdGVTdHJpbmdLZXk6ICdwYXNzd29yZCcsXG4gICAgICAgIHBhc3N3b3JkTGVuZ3RoOiAxNixcbiAgICAgICAgZXhjbHVkZUNoYXJhY3RlcnM6ICdcXFwiQC8nLFxuICAgICAgICBleGNsdWRlUHVuY3R1YXRpb246IHRydWUsXG4gICAgICB9LFxuICAgIH0pO1xuICAgIHJldHVybiBkYXRhYmFzZVNjZXJldDtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgRGF0YWJhc2UgZm9yIEFpcmZsb3dcbiAgICogQHBhcmFtIHByb3BzXG4gICAqIEBwYXJhbSB2cGNcbiAgICogQHJldHVybnNcbiAgICovXG4gIHByaXZhdGUgX2dldEFpcmZsb3dEQih2cGM6IGVjMi5JVnBjLCBkYXRhYmFzZVNjZXJldDogc2VjcmV0c21hbmFnZXIuU2VjcmV0LCBkYk5hbWU6IHN0cmluZyk6IHJkcy5JRGF0YWJhc2VJbnN0YW5jZSB7XG4gICAgY29uc3QgY3JlZGVudGlhbHMgPSByZHMuQ3JlZGVudGlhbHMuZnJvbVNlY3JldChkYXRhYmFzZVNjZXJldCk7XG4gICAgY29uc3QgZGJJbnN0YW5jZSA9IG5ldyByZHMuRGF0YWJhc2VJbnN0YW5jZSh0aGlzLCAnYWlyZmxvdy1kYicsIHtcbiAgICAgIHZwYyxcbiAgICAgIHZwY1N1Ym5ldHM6IHtcbiAgICAgICAgc3VibmV0VHlwZTogZWMyLlN1Ym5ldFR5cGUuSVNPTEFURUQsXG4gICAgICB9LFxuICAgICAgZW5naW5lOiByZHMuRGF0YWJhc2VJbnN0YW5jZUVuZ2luZS5wb3N0Z3Jlcyh7XG4gICAgICAgIHZlcnNpb246IHJkcy5Qb3N0Z3Jlc0VuZ2luZVZlcnNpb24uVkVSXzlfNl8xOCxcbiAgICAgIH0pLFxuICAgICAgY3JlZGVudGlhbHMsXG4gICAgICBpbnN0YW5jZUlkZW50aWZpZXI6ICdhaXJmbG93LWRiJyxcbiAgICAgIGRhdGFiYXNlTmFtZTogZGJOYW1lLFxuICAgICAgcG9ydDogNTQzMixcbiAgICAgIGluc3RhbmNlVHlwZTogZWMyLkluc3RhbmNlVHlwZS5vZihcbiAgICAgICAgZWMyLkluc3RhbmNlQ2xhc3MuQlVSU1RBQkxFMyxcbiAgICAgICAgZWMyLkluc3RhbmNlU2l6ZS5NSUNSTyxcbiAgICAgICksXG4gICAgICBhbGxvY2F0ZWRTdG9yYWdlOiAyMCxcbiAgICAgIHJlbW92YWxQb2xpY3k6IGNkay5SZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICBwYXJhbWV0ZXJHcm91cDogcmRzLlBhcmFtZXRlckdyb3VwLmZyb21QYXJhbWV0ZXJHcm91cE5hbWUodGhpcywgJ2FpcmZsb3ctZGItcGFyYW1ldGVyZ3JvdXAnLCAnZGVmYXVsdC5wb3N0Z3JlczkuNicpLFxuICAgICAgZGVsZXRpb25Qcm90ZWN0aW9uOiBmYWxzZSxcbiAgICAgIHNlY3VyaXR5R3JvdXBzOiBbdGhpcy5kYXRhYmFzZVNHXSxcbiAgICB9KTtcbiAgICByZXR1cm4gZGJJbnN0YW5jZTtcbiAgfVxuXG4gIHByaXZhdGUgX2dldEFpcmZsb3dSZWRpcyhwcm9wczogQWlyZmxvd1Byb3BzLCB2cGM6IGVjMi5JVnBjKTogZWxhc3RpY2FjaGUuQ2ZuQ2FjaGVDbHVzdGVyIHtcbiAgICBjb25zdCByZWRpc05hbWUgPSBwcm9wcy5yZWRpc05hbWUgPz8gJ2FpcmZsb3dyZWRpcyc7XG4gICAgY29uc3QgcmVkaXNDbHVzdGVyID0gbmV3IGVsYXN0aWNhY2hlLkNmbkNhY2hlQ2x1c3Rlcih0aGlzLCAnYWlyZmxvd3JlZGlzJywge1xuICAgICAgZW5naW5lOiAncmVkaXMnLFxuICAgICAgY2FjaGVOb2RlVHlwZTogJ2NhY2hlLnQyLnNtYWxsJyxcbiAgICAgIG51bUNhY2hlTm9kZXM6IDEsXG4gICAgICBwb3J0OiA2Mzc5LFxuICAgICAgY2x1c3Rlck5hbWU6IHJlZGlzTmFtZSxcbiAgICAgIGNhY2hlU3VibmV0R3JvdXBOYW1lOiBuZXcgZWxhc3RpY2FjaGUuQ2ZuU3VibmV0R3JvdXAodGhpcywgJ3JlZGlzc3VibmV0cycsIHtcbiAgICAgICAgZGVzY3JpcHRpb246ICdBaXJmbG93IFJlZGlzIGlzb2xhdGVkIHN1Ym5ldCBncm91cCcsXG4gICAgICAgIHN1Ym5ldElkczogdnBjLmlzb2xhdGVkU3VibmV0cy5tYXAoKHN1Ym5ldCkgPT4gc3VibmV0LnN1Ym5ldElkKSxcbiAgICAgIH0pLnJlZixcbiAgICAgIHZwY1NlY3VyaXR5R3JvdXBJZHM6IFt0aGlzLnJlZGlzU0cuc2VjdXJpdHlHcm91cElkXSxcbiAgICB9KTtcbiAgICByZXR1cm4gcmVkaXNDbHVzdGVyO1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSB0aGUgQXJpZmxvdyBFQ1MgQ2x1c3RlclxuICAgKiBAcGFyYW0gcHJvcHNcbiAgICogQHJldHVybnNcbiAgICovXG4gIHByaXZhdGUgX2dldEFpcmZsb3dFQ1NDbHVzdGVyKHByb3BzOiBBaXJmbG93UHJvcHMsIHZwYzogZWMyLklWcGMsIGJ1Y2tldDogczMuSUJ1Y2tldCwgZGF0YWJhc2VTY2VyZXQ6IHNlY3JldHNtYW5hZ2VyLlNlY3JldCxcbiAgICBkYXRhYmFzZTogcmRzLklEYXRhYmFzZUluc3RhbmNlLCBkYk5hbWU6IHN0cmluZywgcmVkaXM6IG