UNPKG

cdk-serverless-airflow

Version:

[![Build](https://github.com/readybuilderone/serverless-airflow/actions/workflows/build.yml/badge.svg)](https://github.com/readybuilderone/serverless-airflow/actions/workflows/build.yml) [![NPM version](https://badge.fury.io/js/cdk-serverless-airflow.svg)

466 lines 63.3 kB
"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