constructor()

in src/lib/sonatype-nexus3-stack.ts [26:876]


  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const targetRegion = this.node.tryGetContext('region') ?? 'us-east-1';

    const partitionMapping = new cdk.CfnMapping(this, 'PartitionMapping', {
      mapping: {
        'aws': {
          nexus: 'quay.io/travelaudience/docker-nexus',
          nexusProxy: 'quay.io/travelaudience/docker-nexus-proxy',
          albHelmChartRepo: 'https://aws.github.io/eks-charts',
          efsCSIHelmChartRepo: 'https://kubernetes-sigs.github.io/aws-efs-csi-driver/',
          nexusHelmChartRepo: 'https://oteemo.github.io/charts/',
        },
        'aws-cn': {
          nexus: '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/quay/travelaudience/docker-nexus',
          nexusProxy: '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/quay/travelaudience/docker-nexus-proxy',
          albHelmChartRepo: 'https://aws-gcr-solutions-assets.s3.cn-northwest-1.amazonaws.com.cn/helm/charts/eks-charts/',
          efsCSIHelmChartRepo: 'https://aws-gcr-solutions-assets.s3.cn-northwest-1.amazonaws.com.cn/helm/charts/aws-efs-csi-driver/',
          nexusHelmChartRepo: 'https://aws-gcr-solutions-assets.s3.cn-northwest-1.amazonaws.com.cn/helm/charts/oteemo/',
        },
      },
    });

    const constraintDescription = '- at least 8 characters\n- must contain at least 1 uppercase letter, 1 lowercase letter, and 1 number\n- Can contain special characters';
    const adminInitPassword = new cdk.CfnParameter(this, 'NexusAdminInitPassword', {
      type: 'String',
      allowedPattern: '^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$',
      minLength: 8,
      description: `The admin init password of Nexus3. ${constraintDescription}`,
      constraintDescription,
      noEcho: true,
    });
    var hostedZone = null;
    var certificate: certmgr.Certificate | undefined;
    var domainName: string | undefined;

    const internalALB = (/true/i).test(this.node.tryGetContext('internalALB'));
    const r53Domain = internalALB ? undefined : this.node.tryGetContext('r53Domain');

    if (!internalALB) {
      const domainNameParameter = new cdk.CfnParameter(this, 'DomainName', {
        type: 'String',
        allowedPattern: '(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]',
        description: 'The domain name of Nexus OSS deployment, such as mydomain.com.',
        constraintDescription: 'validate domain name without protocol',
      });
      domainName = domainNameParameter.valueAsString;
      if (r53Domain) {
        hostedZone = route53.HostedZone.fromLookup(this, 'R53HostedZone', {
          domainName: r53Domain,
          privateZone: false,
        });
        assert.ok(hostedZone != null, 'Can not find your hosted zone.');
        certificate = new certmgr.Certificate(this, 'SSLCertificate', {
          domainName: domainName,
          validation: certmgr.CertificateValidation.fromDns(hostedZone),
        });
      } else if ((/true/i).test(this.node.tryGetContext('enableR53HostedZone'))) {
        const r53HostedZoneIdParameter = new cdk.CfnParameter(this, 'R53HostedZoneId', {
          type: 'AWS::Route53::HostedZone::Id',
          description: 'The hosted zone ID of given domain name in Route 53.',
        });
        hostedZone = route53.HostedZone.fromHostedZoneId(this, 'ImportedHostedZone', r53HostedZoneIdParameter.valueAsString);
        certificate = new certmgr.Certificate(this, 'SSLCertificate', {
          domainName: domainName,
          validation: certmgr.CertificateValidation.fromDns(hostedZone),
        });
      }
    }

    const logBucket = new s3.Bucket(this, 'LogBucket', {
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      serverAccessLogsPrefix: 'logBucketAccessLog',
    });

    const vpcId = this.node.tryGetContext('vpcId');
    const vpc = vpcId ? ec2.Vpc.fromLookup(this, 'NexusOSSVpc', {
      vpcId: vpcId === 'default' ? undefined : vpcId,
      isDefault: vpcId === 'default' ? true : undefined,
    }) : (() => {
      const newVpc = new ec2.Vpc(this, 'NexusOSSVpc', {
        maxAzs: 2,
      });
      const flowLogPrefix = 'vpcFlowLogs';
      newVpc.addFlowLog('VpcFlowlogs', {
        destination: ec2.FlowLogDestination.toS3(logBucket, flowLogPrefix),
      });
      // https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-s3.html#flow-logs-s3-permissions
      logBucket.addToResourcePolicy(new iam.PolicyStatement({
        sid: 'AWSLogDeliveryWrite',
        principals: [new iam.ServicePrincipal('delivery.logs.amazonaws.com')],
        actions: ['s3:PutObject'],
        resources: [logBucket.arnForObjects(`${flowLogPrefix}/AWSLogs/${cdk.Aws.ACCOUNT_ID}/*`)],
        conditions: {
          StringEquals: {
            's3:x-amz-acl': 'bucket-owner-full-control',
          },
        },
      }));
      logBucket.addToResourcePolicy(new iam.PolicyStatement({
        sid: 'AWSLogDeliveryCheck',
        principals: [new iam.ServicePrincipal('delivery.logs.amazonaws.com')],
        actions: [
          's3:GetBucketAcl',
          's3:ListBucket',
        ],
        resources: [logBucket.bucketArn],
      }));
      return newVpc;
    })();
    if (this.azOfSubnets(vpc.publicSubnets) <= 1 ||
      this.azOfSubnets(vpc.privateSubnets) <= 1) {
      throw new Error(`VPC '${vpc.vpcId}' must have both public and private subnets cross two AZs at least.`);
    }

    const request = require('sync-request');
    const yaml = require('js-yaml');

    const importedEks = this.node.tryGetContext('importedEKS') ?? false;
    var cluster: eks.ICluster;
    var nodeGroup: eks.Nodegroup;
    var eksVersion: cdk.CfnParameter;

    const nexusBlobBucket = new s3.Bucket(this, 'nexus3-blobstore', {
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      encryption: s3.BucketEncryption.S3_MANAGED,
      serverAccessLogsBucket: logBucket,
      serverAccessLogsPrefix: 'blobstoreBucketAccessLog',
      enforceSSL: true,
    });
    if (vpc instanceof ec2.Vpc) {
      const gatewayEndpoint = vpc.addGatewayEndpoint('s3', {
        service: ec2.GatewayVpcEndpointAwsService.S3,
      });
      nexusBlobBucket.addToResourcePolicy(new iam.PolicyStatement({
        effect: iam.Effect.DENY,
        actions: ['s3:*'],
        principals: [new iam.AccountPrincipal(cdk.Aws.ACCOUNT_ID)],
        resources: [
          nexusBlobBucket.bucketArn,
          nexusBlobBucket.arnForObjects('*'),
        ],
        conditions: {
          StringNotEquals: {
            'aws:SourceVpce': gatewayEndpoint.vpcEndpointId,
          },
        },
      }));
    }
    const s3BucketPolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: [
        's3:ListBucket',
      ],
      resources: [nexusBlobBucket.bucketArn],
    });
    const s3ObjectPolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: [
        's3:GetBucketAcl',
        's3:PutObject',
        's3:GetObject',
        's3:DeleteObject',
        's3:PutObjectTagging',
        's3:GetObjectTagging',
        's3:DeleteObjectTagging',
        's3:GetLifecycleConfiguration',
        's3:PutLifecycleConfiguration',
      ],
      resources: [
        nexusBlobBucket.bucketArn,
        nexusBlobBucket.arnForObjects('*'),
      ],
    });

    if (importedEks) {
      if (!vpcId) {throw new Error('Context variable "vpcId" must be specified for imported EKS cluster.');}

      const clusterName = this.node.tryGetContext('eksClusterName');
      const kubectlRoleArn = this.node.tryGetContext('eksKubectlRoleArn');
      const openIdConnectProviderArn = this.node.tryGetContext('eksOpenIdConnectProviderArn');
      const nodeGroupRoleArn = this.node.tryGetContext('nodeGroupRoleArn');

      if (!clusterName || !kubectlRoleArn || !openIdConnectProviderArn || !nodeGroupRoleArn) {throw new Error('Context variables "eksClusterName", "eksKubectlRoleArn", "eksOpenIdConnectProviderArn", "nodeGroupRoleArn" must be specified for imported EKS cluster.');}

      cluster = eks.Cluster.fromClusterAttributes(this, 'ImportedEKS', {
        clusterName,
        kubectlRoleArn,
        openIdConnectProvider: eks.OpenIdConnectProvider.fromOpenIdConnectProviderArn(
          this, 'ImportedClusterOpendIdConnectProvider', openIdConnectProviderArn),
        vpc,
      });
      // the limitation of Nexus3 image not working with IRSA
      const nodeGroupRole = iam.Role.fromRoleArn(this, 'NodeGroupRole', nodeGroupRoleArn, {
        mutable: true,
      });
      nodeGroupRole.attachInlinePolicy(new iam.Policy(this, 'NexusS3BlobStore', {
        statements: [s3BucketPolicy, s3ObjectPolicy],
      }));
    } else {
      const clusterAdmin = new iam.Role(this, 'AdminRole', {
        assumedBy: new iam.AccountRootPrincipal(),
      });

      const isFargetEnabled = (this.node.tryGetContext('enableFarget') || 'false').toLowerCase() === 'true';

      eksVersion = new cdk.CfnParameter(this, 'KubernetesVersion', {
        type: 'String',
        allowedValues: [
          '1.21',
          '1.20',
          '1.19',
          '1.18',
        ],
        default: '1.20',
        description: 'The version of Kubernetes.',
      });

      cluster = new eks.Cluster(this, 'NexusCluster', {
        vpc,
        endpointAccess: eks.EndpointAccess.PRIVATE,
        defaultCapacity: 0,
        mastersRole: clusterAdmin,
        version: eks.KubernetesVersion.of(eksVersion.valueAsString),
        coreDnsComputeType: isFargetEnabled ? eks.CoreDnsComputeType.FARGATE : eks.CoreDnsComputeType.EC2,
      });
      this.setupClusterLogging(cluster);

      if (isFargetEnabled) {
        (cluster as eks.Cluster).addFargateProfile('FargetProfile', {
          selectors: [
            {
              namespace: 'kube-system',
              labels: {
                'k8s-app': 'kube-dns',
              },
            },
          ],
        });
      }

      const template = new ec2.LaunchTemplate(this, 'EKSManagedNodeTemplate', {
        blockDevices: [
          {
            deviceName: '/dev/xvda',
            volume: ec2.BlockDeviceVolume.ebs(30, {
              encrypted: true,
            }),
          },
        ],
        detailedMonitoring: true,
      });
      nodeGroup = (cluster as eks.Cluster).addNodegroupCapacity('nodegroup', {
        nodegroupName: 'nexus3',
        instanceTypes: [
          new ec2.InstanceType(this.node.tryGetContext('instanceType') ?? 'm5.large'),
        ],
        minSize: 1,
        maxSize: 3,
        launchTemplateSpec: {
          id: template.launchTemplateId!,
        },
        labels: {
          usage: 'nexus3',
        },
      });
      // Have to bind IAM role to node due to Nexus3 uses old AWS Java SDK not supporting IRSA
      // see https://github.com/sonatype/nexus-public/pull/69 for detail
      nodeGroup.role.attachInlinePolicy(new iam.Policy(this, 'NexusS3BlobStore', {
        statements: [s3BucketPolicy, s3ObjectPolicy],
      }));

      // install SSM agent as daemonset
      nodeGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));

      (cluster.node.findChild('Resource').node.findChild('CreationRole').node.findChild('DefaultPolicy')
        .node.findChild('Resource') as cdk.CfnResource).addMetadata('cfn_nag', {
        rules_to_suppress: [
          {
            id: 'W12',
            reason: 'wildcard in policy is built by CDK',
          },
        ],
      });

      var ssmManifests = request('GET', 'https://raw.githubusercontent.com/aws-samples/ssm-agent-daemonset-installer/541da0a68a96d5b2ce184724f3d35d22d9ac7236/setup.yaml')
        .getBody('utf-8');

      if (targetRegion.startsWith('cn-')) {
        ssmManifests = ssmManifests.replace('https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm',
          'https://s3.cn-north-1.amazonaws.com.cn/amazon-ssm-cn-north-1/latest/linux_amd64/amazon-ssm-agent.rpm')
          .replace('jicowan/ssm-agent-installer:1.2', '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/dockerhub/jicowan/ssm-agent-installer:1.2')
          .replace('gcr.io/google-containers/pause:2.0', '048912060910.dkr.ecr.cn-northwest-1.amazonaws.com.cn/gcr/google-containers/pause:2.0');
      }
      const ssmInstallerResources = yaml.safeLoadAll(ssmManifests);
      cluster.addManifest('ssm-agent-daemonset', ...ssmInstallerResources);
    }

    // install AWS load balancer via Helm charts
    const awsLoadBalancerControllerVersion = 'v2.2.1';
    const awsControllerBaseResourceBaseUrl = `https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/${awsLoadBalancerControllerVersion}/docs`;
    const awsControllerPolicyUrl = `${awsControllerBaseResourceBaseUrl}/install/iam_policy${targetRegion.startsWith('cn-') ? '_cn' : ''}.json`;
    const albNamespace = 'kube-system';
    const albServiceAccount = cluster.addServiceAccount('aws-load-balancer-controller', {
      name: 'aws-load-balancer-controller',
      namespace: albNamespace,
    });
    const customResourceRole = cdk.Stack.of(this).node.tryFindChild('Custom::AWSCDKOpenIdConnectProviderCustomResourceProvider');
    if (customResourceRole) {
      (customResourceRole.node.findChild('Role') as cdk.CfnResource).addMetadata('cfn_nag', {
        rules_to_suppress: [
          {
            id: 'W11',
            reason: 'wildcard in policy built by CDK',
          },
        ],
      });
    }

    const policyJson = request('GET', awsControllerPolicyUrl).getBody();
    ((JSON.parse(policyJson)).Statement as []).forEach((statement, _idx, _array) => {
      albServiceAccount.addToPrincipalPolicy(iam.PolicyStatement.fromJson(statement));
    });
    const albSAPolicy = albServiceAccount.role.node.children.filter(c => c instanceof iam.Policy)[0].node.defaultChild as iam.CfnPolicy;
    albSAPolicy.addMetadata('cfn_nag', {
      rules_to_suppress: [
        {
          id: 'W76',
          reason: 'the policy statement is from official doc of AWS load balancer controller',
        },
        {
          id: 'W12',
          reason: 'the policy statement is from official doc of AWS load balancer controller',
        },
      ],
    });
    const awsLoadBalancerControllerChart = cluster.addHelmChart('AWSLoadBalancerController', {
      chart: 'aws-load-balancer-controller',
      repository: partitionMapping.findInMap(cdk.Aws.PARTITION, 'albHelmChartRepo'),
      namespace: albNamespace,
      release: 'aws-load-balancer-controller',
      version: '1.2.7', // mapping to v2.2.4
      wait: true,
      timeout: cdk.Duration.minutes(15),
      values: {
        clusterName: cluster.clusterName,
        image: {
          repository: this.getAwsLoadBalancerControllerRepo(),
        },
        serviceAccount: {
          create: false,
          name: albServiceAccount.serviceAccountName,
        },
        // must disable waf features for aws-cn partition
        enableShield: false,
        enableWaf: false,
        enableWafv2: false,
      },
    });

    if (cluster instanceof eks.Cluster) {
      awsLoadBalancerControllerChart.node.addDependency(nodeGroup!);
      awsLoadBalancerControllerChart.node.addDependency(cluster.awsAuth);
    }
    awsLoadBalancerControllerChart.node.addDependency(albServiceAccount);
    awsLoadBalancerControllerChart.node.addDependency(cluster.openIdConnectProvider);

    // deploy EFS, EFS CSI driver, PV
    const efsCSI = cluster.addHelmChart('EFSCSIDriver', {
      chart: 'aws-efs-csi-driver',
      repository: partitionMapping.findInMap(cdk.Aws.PARTITION, 'efsCSIHelmChartRepo'),
      release: 'aws-efs-csi-driver',
      version: '2.2.0', // mapping to v1.3.4
    });
    if (cluster instanceof eks.Cluster) {
      efsCSI.node.addDependency(nodeGroup!);
      efsCSI.node.addDependency(cluster.awsAuth);
    }
    efsCSI.node.addDependency(cluster.openIdConnectProvider);

    const fileSystem = new efs.FileSystem(this, 'Nexus3FileSystem', {
      vpc,
      encrypted: true,
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      throughputMode: efs.ThroughputMode.BURSTING,
    });
    fileSystem.connections.allowDefaultPortFrom(ec2.Peer.ipv4(vpc.vpcCidrBlock),
      'allow access efs from inside vpc');
    fileSystem.connections.securityGroups.forEach(sg =>
      (sg.node.defaultChild as ec2.CfnSecurityGroup).applyRemovalPolicy(cdk.RemovalPolicy.DESTROY));
    const efsClass = 'efs-sc';
    const efsStorageClass = cluster.addManifest('efs-storageclass',
      {
        kind: 'StorageClass',
        apiVersion: 'storage.k8s.io/v1',
        metadata: {
          name: efsClass,
        },
        provisioner: 'efs.csi.aws.com',
      });
    efsStorageClass.node.addDependency(efsCSI);
    const efsPVName = 'nexus3-oss-efs-pv';
    const efsPV = cluster.addManifest('efs-pv', {
      apiVersion: 'v1',
      kind: 'PersistentVolume',
      metadata: {
        name: efsPVName,
      },
      spec: {
        capacity: {
          storage: '1000Gi',
        },
        volumeMode: 'Filesystem',
        accessModes: [
          'ReadWriteMany',
        ],
        persistentVolumeReclaimPolicy: 'Retain',
        storageClassName: efsClass,
        csi: {
          driver: 'efs.csi.aws.com',
          volumeHandle: fileSystem.fileSystemId,
        },
      },
    });
    efsPV.node.addDependency(fileSystem);
    efsPV.node.addDependency(efsStorageClass);

    const nexus3Namespace = 'default';
    const nexus3ChartName = 'nexus3';
    const nexusServiceAccount = cluster.addServiceAccount('sonatype-nexus3', {
      name: 'sonatype-nexus3',
      namespace: nexus3Namespace,
    });

    nexusServiceAccount.addToPolicy(s3BucketPolicy);
    nexusServiceAccount.addToPolicy(s3ObjectPolicy);

    const albLogServiceAccountMapping = new cdk.CfnMapping(this, 'ALBServiceAccountMapping', {
      mapping: {
        'me-south-1': {
          account: '076674570225',
        },
        'eu-south-1': {
          account: '635631232127',
        },
        'ap-northeast-1': {
          account: '582318560864',
        },
        'ap-northeast-2': {
          account: '600734575887',
        },
        'ap-northeast-3': {
          account: '383597477331',
        },
        'ap-south-1': {
          account: '718504428378',
        },
        'ap-southeast-1': {
          account: '114774131450',
        },
        'ap-southeast-2': {
          account: '783225319266',
        },
        'ca-central-1': {
          account: '985666609251',
        },
        'eu-central-1': {
          account: '054676820928',
        },
        'eu-north-1': {
          account: '897822967062',
        },
        'eu-west-1': {
          account: '156460612806',
        },
        'eu-west-2': {
          account: '652711504416',
        },
        'eu-west-3': {
          account: '009996457667',
        },
        'sa-east-1': {
          account: '507241528517',
        },
        'us-east-1': {
          account: '127311923021',
        },
        'us-east-2': {
          account: '033677994240',
        },
        'us-west-1': {
          account: '027434742980',
        },
        'us-west-2': {
          account: '797873946194',
        },
        'ap-east-1': {
          account: '754344448648',
        },
        'af-south-1': {
          account: '098369216593',
        },
        'cn-north-1': {
          account: '638102146993',
        },
        'cn-northwest-1': {
          account: '037604701340',
        },
      },
    });
    const albLogPrefix = 'albAccessLog';
    logBucket.grantPut(new iam.AccountPrincipal(albLogServiceAccountMapping.findInMap(cdk.Aws.REGION, 'account')),
      `${albLogPrefix}/AWSLogs/${cdk.Aws.ACCOUNT_ID}/*`);

    const nexusPort = 8081;
    const healthcheckPath = '/';
    var albOptions = {
      'alb.ingress.kubernetes.io/backend-protocol': 'HTTP',
      'alb.ingress.kubernetes.io/healthcheck-path': healthcheckPath,
      'alb.ingress.kubernetes.io/healthcheck-port': nexusPort,
      'alb.ingress.kubernetes.io/listen-ports': '[{"HTTP": 80}]',
      'alb.ingress.kubernetes.io/scheme': internalALB ? 'internal' : 'internet-facing',
      'alb.ingress.kubernetes.io/inbound-cidrs': internalALB ? vpc.vpcCidrBlock : '0.0.0.0/0',
      'alb.ingress.kubernetes.io/auth-type': 'none',
      'alb.ingress.kubernetes.io/target-type': 'ip',
      'kubernetes.io/ingress.class': 'alb',
      'alb.ingress.kubernetes.io/tags': 'app=nexus3',
      'alb.ingress.kubernetes.io/subnets': vpc.publicSubnets.map(subnet => subnet.subnetId).join(','),
      'alb.ingress.kubernetes.io/load-balancer-attributes': `access_logs.s3.enabled=true,access_logs.s3.bucket=${logBucket.bucketName},access_logs.s3.prefix=${albLogPrefix}`,
    };
    const ingressRules : Array<any> = [
      {
        http: {
          paths: [
            {
              path: '/*',
              backend: {
                serviceName: `${nexus3ChartName}-sonatype-nexus`,
                servicePort: nexusPort,
              },
            },
          ],
        },
      },
    ];
    var externalDNSResource: cdk.Construct;
    if (certificate) {
      Object.assign(albOptions, {
        'alb.ingress.kubernetes.io/certificate-arn': certificate.certificateArn,
        'alb.ingress.kubernetes.io/ssl-policy': 'ELBSecurityPolicy-TLS-1-2-Ext-2018-06',
        'alb.ingress.kubernetes.io/listen-ports': '[{"HTTP": 80}, {"HTTPS": 443}]',
        'alb.ingress.kubernetes.io/actions.ssl-redirect': '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}',
      });

      ingressRules.splice(0, 0, {
        host: domainName,
        http: {
          paths: [
            {
              path: '/*',
              backend: {
                serviceName: 'ssl-redirect',
                servicePort: 'use-annotation',
              },
            },
            {
              path: '/*',
              backend: {
                serviceName: `${nexus3ChartName}-sonatype-nexus`,
                servicePort: nexusPort,
              },
            },
          ],
        },
      });

      // install external dns
      const externalDNSNamespace = 'default';
      const externalDNSServiceAccount = cluster.addServiceAccount('external-dns', {
        name: 'external-dns',
        namespace: externalDNSNamespace,
      });

      const r53ListPolicy = new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          'route53:ListHostedZones',
          'route53:ListResourceRecordSets',
        ],
        resources: ['*'],
      });
      const r53UpdateRecordPolicy = new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          'route53:ChangeResourceRecordSets',
        ],

        resources: [hostedZone!.hostedZoneArn!],
      });
      externalDNSServiceAccount.addToPolicy(r53ListPolicy);
      externalDNSServiceAccount.addToPolicy(r53UpdateRecordPolicy!);

      const externalDNSResources = yaml.safeLoadAll(
        request('GET', `${awsControllerBaseResourceBaseUrl}/examples/external-dns.yaml`)
          .getBody('utf-8').replace('external-dns-test.my-org.com', r53Domain ?? '')
          .replace('my-identifier', 'nexus3'))
        .filter((res: any) => { return res.kind != 'ServiceAccount'; })
        .map((res: any) => {
          if (res.kind === 'Deployment') {
            res.spec.template.spec.containers[0].env = [
              {
                name: 'AWS_REGION',
                value: cdk.Aws.REGION,
              },
            ];
          }
          return res;
        });

      const externalDNS = cluster.addManifest('external-dns', ...externalDNSResources);
      externalDNS.node.addDependency(externalDNSServiceAccount);
      externalDNSResource = externalDNS;
    }

    const enableAutoConfigured: boolean = this.node.tryGetContext('enableAutoConfigured') || false;
    const nexus3ChartVersion = '5.2.1';

    const nexus3PurgeFunc = new lambda_python.PythonFunction(this, 'Nexus3Purge', {
      description: 'Func purges the resources(such as pvc) left after deleting Nexus3 helm chart',
      entry: path.join(__dirname, '../lambda.d/nexus3-purge'),
      index: 'index.py',
      handler: 'handler',
      runtime: lambda.Runtime.PYTHON_3_7,
      environment: cluster.kubectlEnvironment,
      logRetention: logs.RetentionDays.ONE_MONTH,
      timeout: cdk.Duration.minutes(15),
      layers: [
        new AwsCliLayer(this, 'AwsCliLayer'),
        new KubectlLayer(this, 'KubectlLayer'),
      ],
      vpc: vpc,
      securityGroups: cluster.kubectlSecurityGroup ? [cluster.kubectlSecurityGroup] : undefined,
      vpcSubnets: cluster.kubectlPrivateSubnets ? { subnets: cluster.kubectlPrivateSubnets } : undefined,
    });
    nexus3PurgeFunc.role!.addToPrincipalPolicy(new iam.PolicyStatement({
      actions: ['eks:DescribeCluster'],
      resources: [cluster.clusterArn],
    }));
    // allow this handler to assume the kubectl role
    cluster.kubectlRole!.grant(nexus3PurgeFunc.role!, 'sts:AssumeRole');

    const nexus3PurgeCR = new cdk.CustomResource(this, 'Nexus3PurgeCR', {
      serviceToken: nexus3PurgeFunc.functionArn,
      resourceType: 'Custom::Nexus3-Purge',
      properties: {
        ClusterName: cluster.clusterName,
        RoleArn: cluster.kubectlRole!.roleArn,
        ObjectType: 'ingress',
        ObjectName: `${nexus3ChartName}-sonatype-nexus`,
        ObjectNamespace: nexus3Namespace,
        JsonPath: '.status.loadBalancer.ingress[0].hostname',
        TimeoutSeconds: cdk.Duration.minutes(6).toSeconds(),
        Release: nexus3ChartName,
      },
    });
    nexus3PurgeCR.node.addDependency(efsPV);
    nexus3PurgeCR.node.addDependency(awsLoadBalancerControllerChart);

    let nexus3ChartProperties: { [key: string]: any } = {
      statefulset: {
        enabled: true,
      },
      initAdminPassword: {
        enabled: true,
        password: adminInitPassword.valueAsString,
      },
      nexus: {
        imageName: partitionMapping.findInMap(cdk.Aws.PARTITION, 'nexus'),
        resources: {
          requests: {
            memory: '4800Mi',
          },
        },
        livenessProbe: {
          path: healthcheckPath,
        },
      },
      nexusProxy: {
        enabled: false,
      },
      persistence: {
        enabled: true,
        storageClass: efsClass,
        accessMode: 'ReadWriteMany',
      },
      nexusBackup: {
        enabled: false,
        persistence: {
          enabled: false,
        },
      },
      nexusCloudiam: {
        enabled: false,
        persistence: {
          enabled: false,
        },
      },
      ingress: {
        enabled: true,
        path: '/*',
        annotations: albOptions,
        tls: {
          enabled: false,
        },
        rules: ingressRules,
      },
      serviceAccount: {
        create: false,
        // uncomment below line when using IRSA for nexus
        // name: nexusServiceAccount.serviceAccountName,
      },
    };
    if (enableAutoConfigured) {
      // enalbe script feature of nexus3
      nexus3ChartProperties = {
        ...nexus3ChartProperties,
        config: {
          enabled: true,
          data: {
            'nexus.properties': 'nexus.scripts.allowCreation=true',
          },
        },
        deployment: {
          additionalVolumeMounts: [
            {
              mountPath: '/nexus-data/etc/nexus.properties',
              subPath: 'nexus.properties',
              name: 'sonatype-nexus-conf',
            },
          ],
        },
      };
    }
    const nexus3Chart = cluster.addHelmChart('Nexus3', {
      chart: 'sonatype-nexus',
      repository: partitionMapping.findInMap(cdk.Aws.PARTITION, 'nexusHelmChartRepo'),
      namespace: nexus3Namespace,
      release: nexus3ChartName,
      version: nexus3ChartVersion,
      wait: true,
      timeout: cdk.Duration.minutes(15),
      values: nexus3ChartProperties,
    });
    nexus3Chart.node.addDependency(nexusServiceAccount);
    nexus3Chart.node.addDependency(nexus3PurgeCR);
    if (certificate) {
      nexus3PurgeCR.node.addDependency(certificate);
      nexus3Chart.node.addDependency(certificate);
      nexus3Chart.node.addDependency(externalDNSResource!);
    }
    const albAddress = new eks.KubernetesObjectValue(this, 'Nexus3ALBAddress', {
      cluster,
      objectType: 'ingress',
      objectNamespace: nexus3Namespace,
      objectName: `${nexus3ChartName}-sonatype-nexus`,
      jsonPath: '.status.loadBalancer.ingress[0].hostname',
    });
    albAddress.node.addDependency(nexus3Chart);

    if (enableAutoConfigured) {
      const nexusEndpointHostname = `http://${albAddress.value}`;
      if (nexusEndpointHostname) {
        const autoConfigureFunc = new lambda_python.PythonFunction(this, 'Neuxs3AutoCofingure', {
          entry: path.join(__dirname, '../lambda.d/nexuspreconfigure'),
          index: 'index.py',
          handler: 'handler',
          runtime: lambda.Runtime.PYTHON_3_8,
          logRetention: logs.RetentionDays.ONE_MONTH,
          timeout: cdk.Duration.minutes(5),
          vpc: vpc,
        });

        const nexus3AutoConfigureCR = new cdk.CustomResource(this, 'Neuxs3AutoCofingureCustomResource', {
          serviceToken: autoConfigureFunc.functionArn,
          resourceType: 'Custom::Nexus3-AutoConfigure',
          properties: {
            Username: 'admin',
            Password: adminInitPassword.valueAsString,
            Endpoint: nexusEndpointHostname,
            S3BucketName: nexusBlobBucket.bucketName,
          },
        });
        nexus3AutoConfigureCR.node.addDependency(nexus3Chart);

        const addCondition = (): void => {
          if (eksVersion) {
            const eksV119 = new cdk.CfnCondition(this, 'EKSV1.19', {
              expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals('1.19', eksVersion.valueAsString)),
            });
            autoConfigureFunc.node.children.forEach(r => {
              if (r instanceof cdk.CfnResource) {
                (r as cdk.CfnResource).cfnOptions.condition = eksV119;
              } else {
                r.node.children.forEach(r1 => {
                  if (r1 instanceof cdk.CfnResource) {
                    (r1 as cdk.CfnResource).cfnOptions.condition = eksV119;
                  }
                });
              }

            });
            nexus3AutoConfigureCR.node.children.forEach(r => { (r as cdk.CfnResource).cfnOptions.condition = eksV119; });
          }
        };
        addCondition();
      }
    }

    cdk.Aspects.of(cdk.Stack.of(cluster)).add({
      visit: (node: cdk.IConstruct) => {
        if (node instanceof lambda.CfnFunction) {
          node.addPropertyOverride('Environment.Variables.AWS_STS_REGIONAL_ENDPOINTS', 'regional');
        }
      },
    });

    // the hardcode id is copied from https://github.com/aws/aws-cdk/blob/099b5840cc5b45bad987b7e797e6009d6383a3a7/packages/%40aws-cdk/aws-logs/lib/log-retention.ts#L119
    (cdk.Stack.of(this).node.findChild('LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a')
      .node.findChild('ServiceRole').node.findChild('DefaultPolicy').node
      .findChild('Resource') as cdk.CfnResource).addMetadata('cfn_nag', {
      rules_to_suppress: [
        {
          id: 'W12',
          reason: 'wildcard in policy is built by CDK',
        },
      ],
    });

    new cdk.CfnOutput(this, 'nexus-oss-s3-bucket-blobstore', {
      value: `${nexusBlobBucket.bucketName}`,
      description: 'S3 Bucket created for Nexus OSS Blobstore',
    });
    new cdk.CfnOutput(this, 'nexus-oss-alb-domain', {
      value: `${albAddress.value}`,
      description: 'load balancer domain of Nexus OSS',
    });

    this.templateOptions.description = `(SO8020) - Sonatype Nexus Repository OSS on AWS. Template version ${pjson.version}`;
  }