protected async onUpdate()

in packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/cluster-resource-handler/cluster.ts [107:272]


  protected async onUpdate() {
    const updates = analyzeUpdate(this.oldProps, this.newProps);
    console.log('onUpdate:', JSON.stringify({ updates }, undefined, 2));

    // updates to encryption config is not supported
    if (updates.updateEncryption) {
      throw new Error('Cannot update cluster encryption configuration');
    }

    // if there is an update that requires replacement, go ahead and just create
    // a new cluster with the new config. The old cluster will automatically be
    // deleted by cloudformation upon success.
    if (updates.replaceName || updates.replaceRole || updates.updateBootstrapClusterCreatorAdminPermissions ) {
      // if we are replacing this cluster and the cluster has an explicit
      // physical name, the creation of the new cluster will fail with "there is
      // already a cluster with that name". this is a common behavior for
      // CloudFormation resources that support specifying a physical name.
      if (this.oldProps.name === this.newProps.name && this.oldProps.name) {
        throw new Error(`Cannot replace cluster "${this.oldProps.name}" since it has an explicit physical name. Either rename the cluster or remove the "name" configuration`);
      }

      return this.onCreate();
    }

    // validate updates
    const updateTypes = Object.keys(updates).filter(type => type !== 'updateTags') as (keyof UpdateMap)[];
    const enabledUpdateTypes = updateTypes.filter((type) => updates[type]);
    console.log(enabledUpdateTypes);

    if (enabledUpdateTypes.length > 1) {
      throw new Error(
        `Only one type of update - ${updateTypes.join(', ')} can be allowed`,
      );
    }

    // Update tags
    if (updates.updateTags) {
      try {
        // Describe the cluster to get the ARN for tagging APIs and to make sure Cluster exists
        const cluster = (await this.eks.describeCluster({ name: this.clusterName })).cluster;
        if (this.oldProps.tags) {
          if (this.newProps.tags) {
            // This means there are old tags as well as new tags so get the difference
            // Update existing tag keys and add newly added tags
            const tagsToAdd = getTagsToUpdate(this.oldProps.tags, this.newProps.tags);
            if (tagsToAdd) {
              const tagConfig: EKS.TagResourceCommandInput = {
                resourceArn: cluster?.arn,
                tags: tagsToAdd,
              };
              await this.eks.tagResource(tagConfig);
            }
            // Remove the tags that were removed in newProps
            const tagsToRemove = getTagsToRemove(this.oldProps.tags, this.newProps.tags);
            if (tagsToRemove.length > 0 ) {
              const config: EKS.UntagResourceCommandInput = {
                resourceArn: cluster?.arn,
                tagKeys: tagsToRemove,
              };
              await this.eks.untagResource(config);
            }
          } else {
            // This means newProps.tags is empty hence remove all tags
            const config: EKS.UntagResourceCommandInput = {
              resourceArn: cluster?.arn,
              tagKeys: Object.keys(this.oldProps.tags),
            };
            await this.eks.untagResource(config);
          }
        } else {
          // This means oldProps.tags was empty hence add all tags from newProps.tags
          const config: EKS.TagResourceCommandInput = {
            resourceArn: cluster?.arn,
            tags: this.newProps.tags,
          };
          await this.eks.tagResource(config);
        }
      } catch (e: any) {
        throw e;
      }
    }

    // if a version update is required, issue the version update
    if (updates.updateVersion) {
      if (!this.newProps.version) {
        throw new Error(`Cannot remove cluster version configuration. Current version is ${this.oldProps.version}`);
      }

      return this.updateClusterVersion(this.newProps.version);
    }

    if (updates.updateLogging || updates.updateAccess || updates.updateVpc || updates.updateAuthMode) {
      const config: EKS.UpdateClusterConfigCommandInput = {
        name: this.clusterName,
      };
      if (updates.updateLogging) {
        config.logging = this.newProps.logging;
      }
      if (updates.updateAccess) {
        config.resourcesVpcConfig = {
          endpointPrivateAccess: this.newProps.resourcesVpcConfig?.endpointPrivateAccess,
          endpointPublicAccess: this.newProps.resourcesVpcConfig?.endpointPublicAccess,
          publicAccessCidrs: this.newProps.resourcesVpcConfig?.publicAccessCidrs,
        };
      }

      if (updates.updateAuthMode) {
        // update-authmode will fail if we try to update to the same mode,
        // so skip in this case.
        try {
          const cluster = (await this.eks.describeCluster({ name: this.clusterName })).cluster;
          if (cluster?.accessConfig?.authenticationMode === this.newProps.accessConfig?.authenticationMode) {
            console.log(`cluster already at ${cluster?.accessConfig?.authenticationMode}, skipping authMode update`);
            return;
          }
        } catch (e: any) {
          throw e;
        }

        // the update path must be
        // `undefined or CONFIG_MAP` -> `API_AND_CONFIG_MAP` -> `API`
        // and it's one way path.
        // old value is API - cannot fallback backwards
        if (this.oldProps.accessConfig?.authenticationMode === 'API' &&
          this.newProps.accessConfig?.authenticationMode !== 'API') {
          throw new Error(`Cannot fallback authenticationMode from API to ${this.newProps.accessConfig?.authenticationMode}`);
        }
        // old value is API_AND_CONFIG_MAP - cannot fallback to CONFIG_MAP
        if (this.oldProps.accessConfig?.authenticationMode === 'API_AND_CONFIG_MAP' &&
          this.newProps.accessConfig?.authenticationMode === 'CONFIG_MAP') {
          throw new Error(`Cannot fallback authenticationMode from API_AND_CONFIG_MAP to ${this.newProps.accessConfig?.authenticationMode}`);
        }
        // cannot fallback from defined to undefined
        if (this.oldProps.accessConfig?.authenticationMode !== undefined &&
          this.newProps.accessConfig?.authenticationMode === undefined) {
          throw new Error('Cannot fallback authenticationMode from defined to undefined');
        }
        // cannot update from undefined to API because undefined defaults CONFIG_MAP which
        // can only change to API_AND_CONFIG_MAP
        if (this.oldProps.accessConfig?.authenticationMode === undefined &&
          this.newProps.accessConfig?.authenticationMode === 'API') {
          throw new Error('Cannot update from undefined(CONFIG_MAP) to API');
        }
        // cannot update from CONFIG_MAP to API
        if (this.oldProps.accessConfig?.authenticationMode === 'CONFIG_MAP' &&
          this.newProps.accessConfig?.authenticationMode === 'API') {
          throw new Error('Cannot update from CONFIG_MAP to API');
        }
        config.accessConfig = this.newProps.accessConfig;
      }

      if (updates.updateVpc) {
        config.resourcesVpcConfig = {
          subnetIds: this.newProps.resourcesVpcConfig?.subnetIds,
          securityGroupIds: this.newProps.resourcesVpcConfig?.securityGroupIds,
        };
      }

      const updateResponse = await this.eks.updateClusterConfig(config);

      return { EksUpdateId: updateResponse.update?.id };
    }

    // no updates
    return;
  }