Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: resolve single depth references [CFG-1623] #2791

Merged
merged 2 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
TerraformScanInput,
TerraformPlanResourceChange,
IaCErrorCodes,
TerraformPlanReferencedResource,
TerraformPlanExpression,
} from '../types';
import { CustomError } from '../../../../../lib/errors';
import { getErrorStringCode } from '../error-utils';
Expand All @@ -34,6 +36,27 @@ function terraformPlanReducer(
return scanInput;
}

function getExpressions(
expressions: Record<string, TerraformPlanExpression>,
): Record<string, string> {
const result: Record<string, string> = {};
// expressions can be nested. we are only doing 1 depth to resolve top level depenencies
for (const key of Object.keys(expressions)) {
const referenceKey = getReference(expressions[key]);
if (referenceKey) {
result[key] = referenceKey;
}
}
return result;
}

// this is very naive implementation
// the referenences can be composed of number of keys
// we only going to use the first reference for time being
function getReference(value: TerraformPlanExpression): string {
return value.references?.[0];
}

function getResourceName(index: string | number, name: string): string {
return index !== undefined ? `${name}["${index}"]` : name;
}
Expand Down Expand Up @@ -70,24 +93,67 @@ function isValidResourceActions(
});
}

function referencedResourcesResolver(
scanInput: TerraformScanInput,
resources: TerraformPlanReferencedResource[],
): TerraformScanInput {
// check root module for references in first depth of attributes
for (const resource of resources) {
const { type, name, mode, index, expressions } = resource;

// don't care about references in data sources for time being
if (mode == 'data') {
continue;
}
const inputKey: keyof TerraformScanInput = 'resource';

// only update the references in resources that have some resolved attributes already
const resolvedResource: any =
scanInput[inputKey]?.[type]?.[getResourceName(index, name)];
if (resolvedResource) {
const resourceExpressions = getExpressions(expressions);
for (const key of Object.keys(resourceExpressions)) {
// only add non existing attributes. If we already have resolved value do not overwrite it with reference
if (!resolvedResource[key]) {
resolvedResource[key] = resourceExpressions[key];
}
}
scanInput[inputKey][type][
getResourceName(index, name)
] = resolvedResource;
}
}

return scanInput;
}

function extractResourceChanges(
terraformPlanJson: TerraformPlanJson,
): Array<TerraformPlanResourceChange> {
return terraformPlanJson.resource_changes || [];
}

function extractReferencedResources(
terraformPlanJson: TerraformPlanJson,
): Array<TerraformPlanReferencedResource> {
return terraformPlanJson.configuration?.root_module?.resources || [];
}

function extractResourcesForScan(
terraformPlanJson: TerraformPlanJson,
isFullScan = false,
): TerraformScanInput {
const resourceChanges = extractResourceChanges(terraformPlanJson);
return resourceChanges.reduce(
const scanInput = resourceChanges.reduce(
(memo, curr) => resourceChangeReducer(memo, curr, isFullScan),
{
resource: {},
data: {},
},
);

const referencedResources = extractReferencedResources(terraformPlanJson);
return referencedResourcesResolver(scanInput, referencedResources);
}

export function isTerraformPlan(terraformPlanJson: TerraformPlanJson): boolean {
Expand Down
14 changes: 14 additions & 0 deletions src/cli/commands/test/iac-local-execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,21 @@ export interface TerraformPlanResourceChange
export interface TerraformPlanJson {
// there are more values, but these are the required ones for us to scan
resource_changes: Array<TerraformPlanResourceChange>;
configuration: {
root_module: {
resources: Array<TerraformPlanReferencedResource>;
};
};
}

export interface TerraformPlanReferencedResource extends TerraformPlanResource {
expressions: Record<string, TerraformPlanExpression>;
}

export interface TerraformPlanExpression {
references: Array<string>;
}

export interface TerraformScanInput {
// within the resource field, resources are stored: [type] => [name] => [values]
resource: Record<string, Record<string, unknown>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"queued_timeout": 480,
"secondary_artifacts": [],
"secondary_sources": [],
"service_role": "aws_iam_role.terra_ci_job",
"source": [
{
"auth": [],
Expand Down Expand Up @@ -97,10 +98,12 @@
"aws_iam_role_policy": {
"terra_ci_job": {
"name_prefix": null,
"policy": "data.aws_caller_identity.current",
"role": "terra_ci_job"
},
"terra_ci_runner": {
"name_prefix": null,
"policy": "aws_codebuild_project.terra_ci",
"role": "terra_ci_runner"
}
},
Expand Down Expand Up @@ -146,6 +149,7 @@
"terra_ci_runner": {
"definition": "{\n \"Comment\": \"Run Terragrunt Jobs\",\n \"StartAt\": \"OnBranch?\",\n \"States\": {\n \"OnBranch?\": {\n \"Type\": \"Choice\",\n \"Choices\": [\n {\n \"Variable\": \"$.build.sourceversion\",\n \"IsPresent\": true,\n \"Next\": \"PlanBranch\"\n }\n ],\n \"Default\": \"Plan\"\n },\n \"Plan\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::codebuild:startBuild.sync\",\n \"Parameters\": {\n \"ProjectName\": \"terra-ci-runner\",\n \"EnvironmentVariablesOverride\": [\n {\n \"Name\": \"TERRA_CI_BUILD_NAME\",\n \"Value.$\": \"$$.Execution.Name\"\n },\n {\n \"Name\": \"TERRA_CI_RESOURCE\",\n \"Value.$\": \"$.build.environment.terra_ci_resource\"\n }\n ]\n },\n \"End\": true\n },\n \"PlanBranch\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::codebuild:startBuild.sync\",\n \"Parameters\": {\n \"ProjectName\": \"terra-ci-runner\",\n \"SourceVersion.$\": \"$.build.sourceversion\",\n \"EnvironmentVariablesOverride\": [\n {\n \"Name\": \"TERRA_CI_RESOURCE\",\n \"Value.$\": \"$.build.environment.terra_ci_resource\"\n }\n ]\n },\n \"End\": true\n }\n }\n}\n",
"name": "terra-ci-runner",
"role_arn": "aws_iam_role.terra_ci_runner",
"tags": null,
"type": "STANDARD"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"data": {},
"resource": {
"aws_s3_bucket": {
"denied": {
"bucket": "denied",
"bucket_prefix": null,
"force_destroy": false,
"tags": null
},
"duh": {
"bucket": "duh",
"bucket_prefix": null,
"force_destroy": false,
"tags": null
},
"logging2": {
"bucket": "logging2",
"bucket_prefix": null,
"force_destroy": false,
"tags": null
}
},
"aws_s3_bucket_logging": {
"example": {
"bucket": "aws_s3_bucket.logging2.id",
"target_bucket": "aws_s3_bucket.duh.id",
"expected_bucket_owner": null,
"target_grant": [],
"target_prefix": "log/"
},
"example2": {
"bucket": "aws_s3_bucket.logging2.id",
"target_bucket": "aws_s3_bucket.duh.id",
"expected_bucket_owner": null,
"target_grant": [],
"target_prefix": "log/"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"queued_timeout": 480,
"secondary_artifacts": [],
"secondary_sources": [],
"service_role": "aws_iam_role.terra_ci_job",
"source": [
{
"auth": [],
Expand Down Expand Up @@ -97,10 +98,12 @@
"aws_iam_role_policy": {
"terra_ci_job": {
"name_prefix": null,
"policy": "data.aws_caller_identity.current",
"role": "terra_ci_job"
},
"terra_ci_runner": {
"name_prefix": null,
"policy": "aws_codebuild_project.terra_ci",
"role": "terra_ci_runner"
}
},
Expand Down Expand Up @@ -146,6 +149,7 @@
"terra_ci_runner": {
"definition": "{\n \"Comment\": \"Run Terragrunt Jobs\",\n \"StartAt\": \"OnBranch?\",\n \"States\": {\n \"OnBranch?\": {\n \"Type\": \"Choice\",\n \"Choices\": [\n {\n \"Variable\": \"$.build.sourceversion\",\n \"IsPresent\": true,\n \"Next\": \"PlanBranch\"\n }\n ],\n \"Default\": \"Plan\"\n },\n \"Plan\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::codebuild:startBuild.sync\",\n \"Parameters\": {\n \"ProjectName\": \"terra-ci-runner\",\n \"EnvironmentVariablesOverride\": [\n {\n \"Name\": \"TERRA_CI_BUILD_NAME\",\n \"Value.$\": \"$$.Execution.Name\"\n },\n {\n \"Name\": \"TERRA_CI_RESOURCE\",\n \"Value.$\": \"$.build.environment.terra_ci_resource\"\n }\n ]\n },\n \"End\": true\n },\n \"PlanBranch\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::codebuild:startBuild.sync\",\n \"Parameters\": {\n \"ProjectName\": \"terra-ci-runner\",\n \"SourceVersion.$\": \"$.build.sourceversion\",\n \"EnvironmentVariablesOverride\": [\n {\n \"Name\": \"TERRA_CI_RESOURCE\",\n \"Value.$\": \"$.build.environment.terra_ci_resource\"\n }\n ]\n },\n \"End\": true\n }\n }\n}\n",
"name": "terra-ci-runner",
"role_arn": "aws_iam_role.terra_ci_runner",
"tags": null,
"type": "STANDARD"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"data": {},
"resource": {
"aws_s3_bucket": {
"denied": {
"bucket": "denied",
"bucket_prefix": null,
"force_destroy": false,
"tags": null
},
"duh": {
"bucket": "duh",
"bucket_prefix": null,
"force_destroy": false,
"tags": null
},
"logging2": {
"bucket": "logging2",
"bucket_prefix": null,
"force_destroy": false,
"tags": null
}
},
"aws_s3_bucket_logging": {
"example": {
"bucket": "aws_s3_bucket.logging2.id",
"target_bucket": "aws_s3_bucket.duh.id",
"expected_bucket_owner": null,
"target_grant": [],
"target_prefix": "log/"
},
"example2": {
"bucket": "aws_s3_bucket.logging2.id",
"target_bucket": "aws_s3_bucket.duh.id",
"expected_bucket_owner": null,
"target_grant": [],
"target_prefix": "log/"
}
}
}
}
Loading