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;
}