constructor()

in src/network.ts [221:393]


  constructor(scope: constructs.Construct, id: string, props: HyperledgerFabricNetworkProps) {

    super(scope, id);

    // Collect metadata on the stack
    const partition = cdk.Stack.of(this).partition;
    const region = cdk.Stack.of(this).region;
    const account = cdk.Stack.of(this).account;

    // Populate instance variables from input properties, using defaults if values not provided
    this.networkName = props.networkName;
    this.networkDescription = props.networkDescription ?? props.networkName;
    this.memberName = props.memberName;
    this.memberDescription = props.memberDescription ?? props.memberName;
    this.frameworkVersion = props.frameworkVersion ?? FrameworkVersion.VERSION_1_4;
    this.networkEdition = props.networkEdition ?? NetworkEdition.STANDARD;
    this.proposalDurationInHours = props.proposalDurationInHours ?? 24;
    this.thresholdPercentage = props.thresholdPercentage ?? 50;
    this.thresholdComparator = props.thresholdComparator ?? ThresholdComparator.GREATER_THAN;
    this.enableCaLogging = props.enableCaLogging ?? true;

    // Ensure the parameters captured above are valid, so we don't
    // need to wait until deployment time to discover an error
    utilities.validateRegion(region);
    if (!utilities.validateString(this.networkName, 1, 64)) {
      throw new Error('Network name is invalid or not provided. It can be up to 64 characters long.');
    }
    if (!utilities.validateString(this.networkDescription, 0, 128)) {
      throw new Error('Network description is invalid. It can be up to 128 characters long.');
    }
    if (!utilities.validateString(this.memberName, 1, 64, /^(?!-|[0-9])(?!.*-$)(?!.*?--)[a-zA-Z0-9-]+$/)) {
      throw new Error('Member name is invalid or not provided. It can be up to 64 characters long, and can have alphanumeric characters and hyphen(s). It cannot start with a number, or start and end with a hyphen (-), or have two consecutive hyphens. The member name must also be unique across the network.');
    }
    if (!utilities.validateString(this.memberDescription, 0, 128)) {
      throw new Error('Member description is invalid. It can be up to 128 characters long.');
    }
    if (!utilities.validateInteger(this.proposalDurationInHours, 1, 168)) {
      throw new Error('Voting policy proposal duration must be between 1 and 168 hours.');
    }
    if (!utilities.validateInteger(this.thresholdPercentage, 0, 100)) {
      throw new Error('Voting policy threshold percentage must be between 0 and 100.');
    }

    // Per the Managed Blockchain documentation, the admin password must be at least eight
    // characters long and no more than 32 characters. It must contain at least one uppercase
    // letter, one lowercase letter, and one digit. It cannot have a single quotation mark (‘),
    // a double quotation marks (“), a forward slash(/), a backward slash(\), @, or a space;
    // several other characters are exluded here to make the password easier to use in scripts
    const passwordRequirements = {
      passwordLength: 32,
      requireEachIncludedType: true,
      excludeCharacters: '\'"/\\@ &{}<>*|',
    };
    this.adminPasswordSecret = new secretsmanager.Secret(this, 'AdminPassword', { generateSecretString: passwordRequirements });

    // The initially enrolled admin user credentials will be stored in these secrets
    this.adminPrivateKeySecret = new secretsmanager.Secret(this, 'AdminPrivateKey');
    this.adminSignedCertSecret = new secretsmanager.Secret(this, 'AdminSignedCert');

    // Build out the Cloudformation construct for the network/member
    const networkConfiguration = {
      name: this.networkName,
      description: this.networkDescription,
      framework: 'HYPERLEDGER_FABRIC',
      frameworkVersion: this.frameworkVersion,
      networkFrameworkConfiguration: {
        networkFabricConfiguration: {
          edition: this.networkEdition,
        },
      },
      votingPolicy: {
        approvalThresholdPolicy: {
          proposalDurationInHours: this.proposalDurationInHours,
          thresholdPercentage: this.thresholdPercentage,
          thresholdComparator: this.thresholdComparator,
        },
      },
    };
    const memberConfiguration = {
      name: this.memberName,
      description: this.memberDescription,
      memberFrameworkConfiguration: {
        memberFabricConfiguration: {
          adminUsername: 'admin',
          adminPassword: this.adminPasswordSecret.secretValue.toString(),
        },
      },
    };
    const network = new managedblockchain.CfnMember(this, 'Network', { networkConfiguration, memberConfiguration });

    // Capture data included in the Cloudformation output in instance variables
    this.networkId = network.getAtt('NetworkId').toString();
    this.memberId = network.getAtt('MemberId').toString();

    // Build out the associated node constructs
    this.nodes = node.HyperledgerFabricNode.constructNodes(this, props.nodes);

    // Due to a race condition in CDK custom resources (https://github.com/aws/aws-cdk/issues/18237),
    // the necessary permissions for all SDK calls in the stack need to be added here, even though
    // the calls in this construct don't need access to the nodes; this also means node constructs
    // can't populate their outputs fully until later, which is annoying
    const nodeIds = this.nodes.map(n => n.nodeId);
    const nodeArns = nodeIds.map(i => `arn:${partition}:managedblockchain:${region}:${account}:nodes/${i}`);
    const sdkCallPolicy = customresources.AwsCustomResourcePolicy.fromSdkCalls({
      resources: [
        `arn:${partition}:managedblockchain:${region}::networks/${this.networkId}`,
        `arn:${partition}:managedblockchain:${region}:${account}:members/${this.memberId}`,
        ...nodeArns,
      ],
    });

    // Cloudformation doesn't include all the network and member attributes
    // needed to use Hyperledger Fabric, so use SDK calls to fetch said data
    const networkDataSdkCall = {
      service: 'ManagedBlockchain',
      action: 'getNetwork',
      parameters: { NetworkId: this.networkId },
      physicalResourceId: customresources.PhysicalResourceId.of('Id'),
    };
    const memberDataSdkCall = {
      service: 'ManagedBlockchain',
      action: 'getMember',
      parameters: { NetworkId: this.networkId, MemberId: this.memberId },
      physicalResourceId: customresources.PhysicalResourceId.of('Id'),
    };

    // Data items need fetching on creation and updating; nothing needs doing on deletion
    const networkData = new customresources.AwsCustomResource(this, 'NetworkDataResource', {
      policy: sdkCallPolicy,
      onCreate: networkDataSdkCall,
      onUpdate: networkDataSdkCall,
    });
    const memberData = new customresources.AwsCustomResource(this, 'MemberDataResource', {
      policy: sdkCallPolicy,
      onCreate: memberDataSdkCall,
      onUpdate: memberDataSdkCall,
    });

    // Cloudformation doesn't include logging configuration
    // so use SDK call to configure
    const logPublishingConfiguration = {
      Fabric: {
        CaLogs: {
          Cloudwatch: { Enabled: this.enableCaLogging },
        },
      },
    };
    const configureCaLogSdkCall = {
      service: 'ManagedBlockchain',
      action: 'updateMember',
      parameters: { NetworkId: this.networkId, MemberId: this.memberId, LogPublishingConfiguration: logPublishingConfiguration },
      physicalResourceId: customresources.PhysicalResourceId.of('Id'),
    };
    new customresources.AwsCustomResource(this, 'ConfigureCaLogResource', {
      policy: sdkCallPolicy,
      onCreate: configureCaLogSdkCall,
      onUpdate: configureCaLogSdkCall,
    });

    // Grab items out of the above return values and stick them in output properties
    this.vpcEndpointServiceName = networkData.getResponseField('Network.VpcEndpointServiceName');
    this.ordererEndpoint = networkData.getResponseField('Network.FrameworkAttributes.Fabric.OrderingServiceEndpoint');
    this.caEndpoint = memberData.getResponseField('Member.FrameworkAttributes.Fabric.CaEndpoint');

    // As stated earlier, node constructs can't populate all their properties
    // until after the above network and member SDK calls succeed; thus the
    // function calls below where the fetches are split out and logging is configured
    for (const n of this.nodes) {
      n.configureLogging(sdkCallPolicy);
      n.fetchData(sdkCallPolicy);
    }

  }