Skip to content

Commit

Permalink
Fix property normalization for properties with additionalProperties (
Browse files Browse the repository at this point in the history
…#192)

Some resources properties are an object type with additionalProperties.
These properties should not have their keys transformed, but should have
their values.
  • Loading branch information
corymhall authored Nov 1, 2024
1 parent e1ce154 commit 33cb289
Show file tree
Hide file tree
Showing 3 changed files with 719 additions and 246 deletions.
85 changes: 76 additions & 9 deletions src/pulumi-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,28 @@ export interface PulumiType {

/**
* The schema for the array items property
* A property will typically have either $ref, properties or additionalProperties
*/
export interface PulumiPropertyItems {
/**
* A reference to another type
*/
$ref?: string;

/**
* The properties of an object type
*/
properties?: { [key: string]: PulumiPropertyItems };

/**
* The simple type (i.e. string, number, etc)
*/
type?: string;

/**
* A type with additional properties
*/
additionalProperties?: PulumiPropertyItems;
}

/**
Expand Down Expand Up @@ -113,6 +124,12 @@ export enum NativeType {
* The type is not a json type and should be processed
*/
NON_JSON = 'NON_JSON',

/**
* The type an additional properties type which means the keys
* should not be processed but the values should be
*/
ADDITIONAL_PROPERTIES = 'ADDITIONAL_PROPERTIES',
}

/**
Expand All @@ -136,7 +153,49 @@ export function processMetadataProperty(
meta?: { [key: string]: PulumiProperty };
} {
switch (true) {
case property.type !== undefined && property.$ref === undefined:
// Objects with `additionalProperties` can have arbitrary keys that should not be transformed
// for example
//
// Case 1: additionalProperties that is a JSON type should return `NativeType.JSON` and treat the
// entire object as a JSON object
// {
// "targetResources": {
// "additionalProperties": {
// "$ref": "pulumi.json#/Any"
// }
// }
// }
//
// Case 2: additionalProperties that is a non-JSON type should return `NativeType.ADDITIONAL_PROPERTIES`
// so that the keys are not transformed but the values are
// {
// "throttle": {
// "additionalProperties": {
// "$ref": "#/types/pulumi:aws-native/aws:apigateway/UsagePlanThrottleSettings"
// }
// }
// }
// {
// "aws-native:aws:apigateway/UsagePlanThrottleSettings": {
// "type": "object",
// "properties": {
// "burstLimit": {
// "type": "integer"
// }
// }
// }
// }
case property.type === 'object' && property.additionalProperties !== undefined: {
const props = processMetadataProperty(property.additionalProperties, types, pulumiProvider);
return {
meta: props.meta,
nativeType: props.nativeType === NativeType.JSON ? props.nativeType : NativeType.ADDITIONAL_PROPERTIES,
};
}
case property.type !== undefined &&
property.$ref === undefined &&
property.properties === undefined &&
property.additionalProperties === undefined:
if (property.type === 'object') {
return { nativeType: NativeType.JSON };
}
Expand All @@ -160,35 +219,41 @@ export function processMetadataProperty(
}

/**
* Determines whether or not the property in question is a JSON type of not.
* Determines the type of the provided property.
*
* If the property is a nested property then the `propName` will contain all the parent
* properties so that the correct nested type can be found
*
* @param properties The resource properties
* @param propName the property name as a list containing parent property names
* @param types The pulumi types
* @param pulumiProvider The pulumi provider to read the schema from.
* @returns true if the property is a JSON type and should not be normalized
* @returns the NativeType of the property
*/
export function isJsonType(
export function getNativeType(
propName: string[],
properties: { [key: string]: PulumiProperty },
types: { [key: string]: PulumiType },
pulumiProvider: PulumiProvider,
): boolean {
): NativeType {
let props = properties;
let typ: NativeType = NativeType.NON_JSON;
for (let i = 0; i < propName.length; i++) {
const prop = toSdkName(propName[i]);
if (prop in props) {
const metaProp = props[prop];

const { nativeType, meta } = processMetadataProperty(metaProp, types, pulumiProvider);
if (nativeType === NativeType.JSON) {
return true;
return nativeType;
}
typ = nativeType ?? NativeType.NON_JSON;
props = meta!;
} else {
return NativeType.NON_JSON;
}
}
return false;
return typ;
}

/**
Expand Down Expand Up @@ -222,12 +287,14 @@ export function normalizeObject(key: string[], value: any, cfnType?: string, pul
pulumiProvider = pulumiProvider ?? PulumiProvider.AWS_NATIVE;
const metadata = new Metadata(pulumiProvider);
const resource = metadata.findResource(cfnType);
if (isJsonType(key, resource.inputs, metadata.types(), pulumiProvider)) {
const nativeType = getNativeType(key, resource.inputs, metadata.types(), pulumiProvider);
if (nativeType === NativeType.JSON) {
return value;
}

Object.entries(value).forEach(([k, v]) => {
result[toSdkName(k)] = normalizeObject([...key, k], v, cfnType);
k = nativeType === NativeType.ADDITIONAL_PROPERTIES ? k : toSdkName(k);
result[k] = normalizeObject([...key, k], v, cfnType);
});
return result;
} catch (e) {
Expand Down
Loading

0 comments on commit 33cb289

Please sign in to comment.