Skip to content

Commit

Permalink
feature(terraform): Parse and auto detect TF module dependencies (#298)
Browse files Browse the repository at this point in the history
It could be tedious to manually manage all terraform module calls as
dependencies for larger projects. This parses the HCL source to identify
and add direct static dependencies to the NX dependency graph.
  • Loading branch information
TriPSs authored Aug 19, 2024
2 parents c2fca9d + b1631eb commit c970e53
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 10 deletions.
5 changes: 4 additions & 1 deletion packages/terraform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
"@nx/devkit": "^19.6.0"
},
"builders": "./executors.json",
"generators": "./generators.json"
"generators": "./generators.json",
"dependencies": {
"hcl2-json-parser": "^1.0.1"
}
}
76 changes: 76 additions & 0 deletions packages/terraform/src/graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
CreateDependencies,
logger,
RawProjectGraphDependency,
workspaceRoot
} from '@nx/devkit'
import * as hcl2JsonParser from 'hcl2-json-parser'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import { DependencyType } from 'nx/src/config/project-graph'

const isLocalPath = (path: string) => {
return path.startsWith('./') || path.startsWith('../')
}

export const createDependencies: CreateDependencies = async (_, ctx) => {
const results: RawProjectGraphDependency[] = []

const projectRootsToProject: [projectRoot: string, name: string][] =
Object.entries(ctx.projects).map(([name, project]) => [project.root, name])

for (const project of Object.keys(ctx.projects)) {
// find tf files to process in the project
const tfFilesToProcess =
ctx.filesToProcess.projectFileMap[project]?.filter((file) =>
file.file.endsWith('.tf')
) ?? []

for (const file of tfFilesToProcess) {
const data = await fs.readFile(file.file)

let parsed: hcl2JsonParser.HclDef

try {
parsed = await hcl2JsonParser.parseToObject(data.toString())
} catch (e) {
logger.warn(
`Failed to parse .tf file ${file.file}. Error: ${e.message}`
)
continue
}

for (const moduleCall of Object.values(parsed.module ?? [])) {
const depSourcePathRel = moduleCall[0]?.source

if (!isLocalPath(depSourcePathRel)) {
continue
}

const depSourceAbs = path.resolve(file.file, depSourcePathRel)

const depSourceRelativeToWorkspace = path.relative(
workspaceRoot,
depSourceAbs
)

const targetProject = projectRootsToProject.find(([root]) =>
depSourceRelativeToWorkspace.startsWith(root)
)?.[1]

if (!targetProject || targetProject === project) {
continue
}

results.push({
type: DependencyType.static,
source: project,
target: targetProject,
sourceFile: file.file
})
}
}
}

return results
}
27 changes: 27 additions & 0 deletions packages/terraform/src/hcl2-json-parser.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
declare module 'hcl2-json-parser' {
export type HclDef = {
data?: {
[data: string]: {
[dataName: string]: [{ [propertyName: string]: any }]
}
}

resource?: {
[resource: string]: {
[resourceName: string]: [{ [propertyName: string]: any }]
}
}

terraform?: [
{
backend?: [{ [propertyName: string]: any }]
}
]

module?: {
[moduleName: string]: [{ source: string }]
}
}

export function parseToObject(data: string): Promise<HclDef>
}
1 change: 1 addition & 0 deletions packages/terraform/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './graph'
Loading

0 comments on commit c970e53

Please sign in to comment.