Skip to content

Commit

Permalink
fix(hcl2cdk): harden reference finding and centralize variable name g…
Browse files Browse the repository at this point in the history
…eneration
  • Loading branch information
DanielMSchmidt committed Jul 7, 2021
1 parent 0003972 commit 5864857
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 227 deletions.
50 changes: 35 additions & 15 deletions packages/@cdktf/hcl2cdk/lib/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export type Reference = {
referencee: { id: string; full: string }; // identifier for resource
};

export function extractReferencesFromExpression(input: string): Reference[] {
export function extractReferencesFromExpression(
input: string,
nodeIds: readonly string[]
): Reference[] {
if (input.includes(".*")) {
throw new Error(
`Unsupported Terraform feature found: Splat operations (resource.name.*.property) are not yet supported: ${input}`
Expand Down Expand Up @@ -75,32 +78,49 @@ export function extractReferencesFromExpression(input: string): Reference[] {
return carry;
}

const [start, ...referenceParts] = spot.split(".");
const referenceParts = spot.split(".");

const corespondingNodeId = nodeIds.find((id) => {
const parts = id.split(".");
const matchesFirstTwo =
parts[0] === referenceParts[0] && parts[1] === referenceParts[1];
return (
matchesFirstTwo &&
(parts[0] === "data" ? parts[2] === referenceParts[2] : true)
);
});

const id = (
start === "data"
? [start, referenceParts[0], referenceParts[1]]
: [start, referenceParts[0]]
).join(".");
if (!corespondingNodeId) {
throw new Error(
`Found a reference that is unknown: ${input} was not found in ${JSON.stringify(
nodeIds
)}`
);
}

const ref: Reference = {
start: input.indexOf(spot),
end: input.indexOf(spot) + spot.length,
referencee: { id, full: spot },
referencee: { id: corespondingNodeId, full: spot },
};
return [...carry, ref];
}, [] as Reference[]);
}

function referenceToAst(ref: Reference) {
const [resource, name, ...selector] = ref.referencee.full.split(".");
export function referenceToVariableName(ref: Reference): string {
const [resource, name] = ref.referencee.full.split(".");
return camelCase(
["var", "local", "module"].includes(resource)
? name
: [resource, name].join("_")
);
}

export function referenceToAst(ref: Reference) {
const [resource, _name, ...selector] = ref.referencee.full.split(".");

const variableReference = t.identifier(
camelCase(
["var", "local", "module"].includes(resource)
? name
: [resource, name].join("_")
)
camelCase(referenceToVariableName(ref))
);

return selector.reduce(
Expand Down
105 changes: 75 additions & 30 deletions packages/@cdktf/hcl2cdk/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ import {
Reference,
extractReferencesFromExpression,
referencesToAst,
referenceToVariableName,
} from "./expressions";

const valueToTs = (item: any): t.Expression => {
const valueToTs = (item: any, nodeIds: readonly string[]): t.Expression => {
switch (typeof item) {
case "string":
return referencesToAst(item, extractReferencesFromExpression(item));
return referencesToAst(
item,
extractReferencesFromExpression(item, nodeIds)
);
case "boolean":
return t.booleanLiteral(item);
case "number":
return t.numericLiteral(item);
case "object":
if (Array.isArray(item)) {
return t.arrayExpression(item.map(valueToTs));
return t.arrayExpression(item.map((i) => valueToTs(i, nodeIds)));
}

if (
Expand All @@ -41,7 +45,10 @@ const valueToTs = (item: any): t.Expression => {
Object.entries(item)
.filter(([_key, value]) => value !== undefined)
.map(([key, value]) =>
t.objectProperty(t.stringLiteral(camelCase(key)), valueToTs(value))
t.objectProperty(
t.stringLiteral(camelCase(key)),
valueToTs(value, nodeIds)
)
)
);
}
Expand Down Expand Up @@ -74,15 +81,7 @@ function findUsedReferences(
}

if (typeof item === "string") {
const extractedRefs = extractReferencesFromExpression(item);
if (!extractedRefs.every((ref) => nodeIds.includes(ref.referencee.id))) {
throw new Error(
`Found a reference that is unknown: ${item} resulted in references ${JSON.stringify(
extractedRefs
)}, but only these identifiers are known: ${JSON.stringify(nodeIds)}`
);
}

const extractedRefs = extractReferencesFromExpression(item, nodeIds);
return [...references, ...extractedRefs];
}
return references;
Expand All @@ -92,7 +91,8 @@ function asExpression(
type: string,
name: string,
config: any,
isReferenced: boolean
nodeIds: readonly string[],
reference?: Reference
) {
const isNamespacedImport = type.includes(".");
const subject = isNamespacedImport
Expand All @@ -105,15 +105,24 @@ function asExpression(
const expression = t.newExpression(subject, [
t.thisExpression(),
t.stringLiteral(name),
valueToTs(config),
valueToTs(config, nodeIds),
]);
return isReferenced
return reference
? t.variableDeclaration("const", [
t.variableDeclarator(t.identifier(camelCase(name)), expression),
t.variableDeclarator(
t.identifier(referenceToVariableName(reference)),
expression
),
])
: t.expressionStatement(expression);
}
function output(key: string, _id: string, item: Output): t.Statement {
function output(
key: string,
_id: string,
item: Output,
graph: DirectedGraph
): t.Statement {
const nodeIds = graph.nodes();
const [{ value, description, sensitive }] = item;

return asExpression(
Expand All @@ -124,7 +133,7 @@ function output(key: string, _id: string, item: Output): t.Statement {
description,
sensitive,
},
false
nodeIds
);
}

Expand All @@ -136,13 +145,29 @@ function variable(
): t.Statement {
// We don't handle type information right now
const [{ type, ...props }] = item;
const nodeIds = graph.nodes();

return asExpression("TerraformVariable", key, props, isReferenced(graph, id));
return asExpression(
"TerraformVariable",
key,
props,
nodeIds,
getReference(graph, id)
);
}

function local(key: string, _id: string, item: any): t.Statement {
function local(
key: string,
_id: string,
item: any,
graph: DirectedGraph
): t.Statement {
const nodeIds = graph.nodes();
return t.variableDeclaration("const", [
t.variableDeclarator(t.identifier(camelCase(key)), valueToTs(item)),
t.variableDeclarator(
t.identifier(camelCase(key)),
valueToTs(item, nodeIds)
),
]);
}

Expand All @@ -153,10 +178,18 @@ function modules(
graph: DirectedGraph
): t.Statement {
const [{ source, ...props }] = item;
return asExpression(source, key, props, isReferenced(graph, id));
const nodeIds = graph.nodes();

return asExpression(source, key, props, nodeIds, getReference(graph, id));
}

function provider(key: string, _id: string, item: Provider): t.Statement {
function provider(
key: string,
_id: string,
item: Provider,
graph: DirectedGraph
): t.Statement {
const nodeIds = graph.nodes();
const props = item[0];

if (props.alias) {
Expand All @@ -169,11 +202,21 @@ function provider(key: string, _id: string, item: Provider): t.Statement {
`${key}.${pascalCase(key + "Provider")}`,
key,
props,
false
nodeIds
);
}
function isReferenced(graph: DirectedGraph, id: string) {
return graph.outNeighbors(id).length > 0;
function getReference(graph: DirectedGraph, id: string) {
const neighbors = graph.outNeighbors(id);
if (neighbors.length > 0) {
const edge = graph.directedEdge(id, neighbors[0]);
if (edge) {
return graph.getEdgeAttribute(edge, "ref") as Reference;
} else {
return undefined;
}
} else {
return undefined;
}
}

function resource(
Expand All @@ -184,11 +227,13 @@ function resource(
graph: DirectedGraph
): t.Statement {
const [provider, ...name] = type.split("_");
const nodeIds = graph.nodes();
return asExpression(
`${provider}.${name.join("_")}`,
key,
item[0],
isReferenced(graph, id)
nodeIds,
getReference(graph, id)
);
}

Expand Down Expand Up @@ -266,7 +311,7 @@ export async function convertToTypescript(filename: string, hcl: string) {
function addGlobalEdges(_key: string, id: string, value: unknown) {
findUsedReferences(nodeIds, value).forEach((ref) => {
if (!graph.hasDirectedEdge(ref.referencee.id, id)) {
graph.addDirectedEdge(ref.referencee.id, id);
graph.addDirectedEdge(ref.referencee.id, id, { ref });
}
});
}
Expand All @@ -278,7 +323,7 @@ export async function convertToTypescript(filename: string, hcl: string) {
) {
findUsedReferences(nodeIds, value).forEach((ref) => {
if (!graph.hasDirectedEdge(ref.referencee.id, id)) {
graph.addDirectedEdge(ref.referencee.id, id);
graph.addDirectedEdge(ref.referencee.id, id, { ref });
}
});
}
Expand Down
Loading

0 comments on commit 5864857

Please sign in to comment.