Skip to content

Commit 1d205b7

Browse files
authoredMay 6, 2021
Merge pull request #1891 from snyk/fix/terraform-plan-full-scan
fix: tf plan full scan
2 parents 9c6dcfd + ac6208d commit 1d205b7

19 files changed

+5338
-587
lines changed
 

‎src/cli/commands/test/iac-local-execution/parsers/terraform-plan-parser.ts

+22-46
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
TerraformPlanJson,
66
TerraformPlanResource,
77
ResourceActions,
8-
VALID_RESOURCE_ACTIONS,
8+
VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN,
9+
VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN,
910
TerraformScanInput,
1011
TerraformPlanResourceChange,
1112
IaCErrorCodes,
@@ -35,19 +36,26 @@ function terraformPlanReducer(
3536
function resourceChangeReducer(
3637
scanInput: TerraformScanInput,
3738
resource: TerraformPlanResourceChange,
39+
isFullScan: boolean,
3840
): TerraformScanInput {
3941
// TODO: investigate if we need to address also `after_unknown` field.
4042
const { actions, after } = resource.change || { actions: [], after: {} };
41-
if (isValidResourceActions(actions)) {
43+
if (isValidResourceActions(actions, isFullScan)) {
4244
const resourceForReduction = { ...resource, values: after || {} };
4345
return terraformPlanReducer(scanInput, resourceForReduction);
4446
}
4547

4648
return scanInput;
4749
}
4850

49-
function isValidResourceActions(action: ResourceActions): boolean {
50-
return VALID_RESOURCE_ACTIONS.some((validAction: string[]) => {
51+
function isValidResourceActions(
52+
action: ResourceActions,
53+
isFullScan: boolean,
54+
): boolean {
55+
const VALID_ACTIONS = isFullScan
56+
? VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN
57+
: VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN;
58+
return VALID_ACTIONS.some((validAction: string[]) => {
5159
if (action.length !== validAction.length) {
5260
return false;
5361
}
@@ -57,56 +65,28 @@ function isValidResourceActions(action: ResourceActions): boolean {
5765
});
5866
}
5967

60-
function extractRootModuleResources(
61-
terraformPlanJson: TerraformPlanJson,
62-
): Array<TerraformPlanResource> {
63-
return terraformPlanJson.planned_values?.root_module?.resources || [];
64-
}
65-
66-
function extractChildModulesResources(
67-
terraformPlanJson: TerraformPlanJson,
68-
): Array<TerraformPlanResource> {
69-
const childModules =
70-
terraformPlanJson?.planned_values?.root_module?.child_modules || [];
71-
const extractedChildModuleResources: Array<TerraformPlanResource> = [];
72-
childModules.forEach((childModule) =>
73-
childModule.resources.forEach((resource) =>
74-
extractedChildModuleResources.push(resource),
75-
),
76-
);
77-
return extractedChildModuleResources;
78-
}
79-
8068
function extractResourceChanges(
8169
terraformPlanJson: TerraformPlanJson,
8270
): Array<TerraformPlanResourceChange> {
8371
return terraformPlanJson.resource_changes || [];
8472
}
8573

86-
function extractResourcesForFullScan(
87-
terraformPlanJson: TerraformPlanJson,
88-
): TerraformScanInput {
89-
const rootModuleResources = extractRootModuleResources(terraformPlanJson);
90-
const childModuleResources = extractChildModulesResources(terraformPlanJson);
91-
return [
92-
...rootModuleResources,
93-
...childModuleResources,
94-
].reduce(terraformPlanReducer, { resource: {}, data: {} });
95-
}
96-
97-
function extractResourcesForDeltaScan(
74+
function extractResourcesForScan(
9875
terraformPlanJson: TerraformPlanJson,
76+
isFullScan = false,
9977
): TerraformScanInput {
10078
const resourceChanges = extractResourceChanges(terraformPlanJson);
101-
return resourceChanges.reduce(resourceChangeReducer, {
102-
resource: {},
103-
data: {},
104-
});
79+
return resourceChanges.reduce(
80+
(memo, curr) => resourceChangeReducer(memo, curr, isFullScan),
81+
{
82+
resource: {},
83+
data: {},
84+
},
85+
);
10586
}
10687

10788
export function isTerraformPlan(terraformPlanJson: TerraformPlanJson): boolean {
10889
const missingRequiredFields =
109-
!terraformPlanJson.planned_values?.root_module ||
11090
terraformPlanJson.resource_changes === undefined;
11191
return !missingRequiredFields;
11292
}
@@ -117,14 +97,10 @@ export function tryParsingTerraformPlan(
11797
{ isFullScan }: { isFullScan: boolean } = { isFullScan: false },
11898
): Array<IacFileParsed> {
11999
try {
120-
const scannableInput = isFullScan
121-
? extractResourcesForFullScan(terraformPlanJson)
122-
: extractResourcesForDeltaScan(terraformPlanJson);
123-
124100
return [
125101
{
126102
...terraformPlanFile,
127-
jsonContent: scannableInput,
103+
jsonContent: extractResourcesForScan(terraformPlanJson, isFullScan),
128104
engineType: EngineType.Terraform,
129105
},
130106
];

‎src/cli/commands/test/iac-local-execution/types.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,6 @@ export interface TerraformPlanResourceChange
170170

171171
export interface TerraformPlanJson {
172172
// there are more values, but these are the required ones for us to scan
173-
planned_values: {
174-
root_module: {
175-
resources: Array<TerraformPlanResource>;
176-
child_modules: Array<{ resources: Array<TerraformPlanResource> }>;
177-
};
178-
};
179173
resource_changes: Array<TerraformPlanResourceChange>;
180174
}
181175
export interface TerraformScanInput {
@@ -195,13 +189,19 @@ export type ResourceActions =
195189
| ['delete'];
196190

197191
// we will be scanning the `create` & `update` actions only.
198-
export const VALID_RESOURCE_ACTIONS: ResourceActions[] = [
192+
export const VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN: ResourceActions[] = [
199193
['create'],
200194
['update'],
201195
['create', 'delete'],
202196
['delete', 'create'],
203197
];
204198

199+
// scans all actions including 'no-op' in order to iterate on all resources.
200+
export const VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN: ResourceActions[] = [
201+
['no-op'],
202+
...VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN,
203+
];
204+
205205
// Error codes used for Analytics & Debugging
206206
// Within a single module, increments are in 1.
207207
// Between modules, increments are in 10, according to the order of execution.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"resource": {
3+
"aws_codebuild_project": {
4+
"terra_ci": {
5+
"artifacts": [
6+
{
7+
"artifact_identifier": null,
8+
"encryption_disabled": false,
9+
"location": "terra-ci-artifacts-eu-west-1-000002",
10+
"name": null,
11+
"namespace_type": null,
12+
"override_artifact_name": false,
13+
"packaging": null,
14+
"path": null,
15+
"type": "S3"
16+
}
17+
],
18+
"badge_enabled": false,
19+
"build_timeout": 10,
20+
"cache": [],
21+
"description": "Deploy environment configuration",
22+
"environment": [
23+
{
24+
"certificate": null,
25+
"compute_type": "BUILD_GENERAL1_SMALL",
26+
"environment_variable": [],
27+
"image": "aws/codebuild/amazonlinux2-x86_64-standard:2.0",
28+
"image_pull_credentials_type": "CODEBUILD",
29+
"privileged_mode": false,
30+
"registry_credential": [],
31+
"type": "LINUX_CONTAINER"
32+
}
33+
],
34+
"logs_config": [
35+
{
36+
"cloudwatch_logs": [
37+
{
38+
"group_name": null,
39+
"status": "ENABLED",
40+
"stream_name": null
41+
}
42+
],
43+
"s3_logs": [
44+
{
45+
"encryption_disabled": false,
46+
"location": null,
47+
"status": "DISABLED"
48+
}
49+
]
50+
}
51+
],
52+
"name": "terra-ci-runner",
53+
"queued_timeout": 480,
54+
"secondary_artifacts": [],
55+
"secondary_sources": [],
56+
"source": [
57+
{
58+
"auth": [],
59+
"buildspec": "version: 0.2\nphases:\n install:\n commands:\n - make install_tools\n build:\n commands:\n - make plan_local resource=$TERRA_CI_RESOURCE\nartifacts:\n files:\n - ./tfplan\n name: $TERRA_CI_BUILD_NAME\n\n",
60+
"git_clone_depth": 1,
61+
"git_submodules_config": [],
62+
"insecure_ssl": false,
63+
"location": "https://github.com/p0tr3c-terraform/terra-ci-single-account.git",
64+
"report_build_status": false,
65+
"type": "GITHUB"
66+
}
67+
],
68+
"source_version": null,
69+
"tags": null,
70+
"vpc_config": []
71+
}
72+
},
73+
"aws_iam_role": {
74+
"terra_ci_job": {
75+
"assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"codebuild.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n",
76+
"description": null,
77+
"force_detach_policies": false,
78+
"max_session_duration": 3600,
79+
"name": "terra_ci_job",
80+
"name_prefix": null,
81+
"path": "/",
82+
"permissions_boundary": null,
83+
"tags": null
84+
},
85+
"terra_ci_runner": {
86+
"assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"states.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n",
87+
"description": null,
88+
"force_detach_policies": false,
89+
"max_session_duration": 3600,
90+
"name": "terra_ci_runner",
91+
"name_prefix": null,
92+
"path": "/",
93+
"permissions_boundary": null,
94+
"tags": null
95+
}
96+
},
97+
"aws_iam_role_policy": {
98+
"terra_ci_job": {
99+
"name_prefix": null,
100+
"role": "terra_ci_job"
101+
},
102+
"terra_ci_runner": {
103+
"name_prefix": null,
104+
"role": "terra_ci_runner"
105+
}
106+
},
107+
"aws_iam_role_policy_attachment": {
108+
"terra_ci_job_ecr_access": {
109+
"policy_arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser",
110+
"role": "terra_ci_job"
111+
}
112+
},
113+
"aws_s3_bucket": {
114+
"terra_ci": {
115+
"acl": "private",
116+
"bucket": "terra-ci-artifacts-eu-west-1-000002",
117+
"bucket_prefix": null,
118+
"cors_rule": [],
119+
"force_destroy": false,
120+
"grant": [],
121+
"lifecycle_rule": [],
122+
"logging": [],
123+
"object_lock_configuration": [],
124+
"policy": null,
125+
"replication_configuration": [],
126+
"server_side_encryption_configuration": [
127+
{
128+
"rule": [
129+
{
130+
"apply_server_side_encryption_by_default": [
131+
{
132+
"kms_master_key_id": null,
133+
"sse_algorithm": "aws:kms"
134+
}
135+
],
136+
"bucket_key_enabled": false
137+
}
138+
]
139+
}
140+
],
141+
"tags": null,
142+
"website": []
143+
}
144+
},
145+
"aws_sfn_state_machine": {
146+
"terra_ci_runner": {
147+
"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",
148+
"name": "terra-ci-runner",
149+
"tags": null,
150+
"type": "STANDARD"
151+
}
152+
}
153+
},
154+
"data": {}
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"resource": {},
3+
"data": {}
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"resource": {},
3+
"data": {}
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"resource": {
3+
"aws_codebuild_project": {
4+
"some_projed": {
5+
"arn": "arn:aws:codebuild:eu-west-1:719261439472:project/why-my-project-not-working",
6+
"artifacts": [
7+
{
8+
"artifact_identifier": "",
9+
"encryption_disabled": true,
10+
"location": "terra-ci-artifacts-eu-west-1-000002",
11+
"name": "why-my-project-not-working",
12+
"namespace_type": "NONE",
13+
"override_artifact_name": true,
14+
"packaging": "NONE",
15+
"path": "",
16+
"type": "S3"
17+
}
18+
],
19+
"badge_enabled": false,
20+
"badge_url": "",
21+
"build_timeout": 10,
22+
"cache": [
23+
{
24+
"location": "",
25+
"modes": [],
26+
"type": "NO_CACHE"
27+
}
28+
],
29+
"description": "Deploy environment configuration",
30+
"encryption_key": "arn:aws:kms:eu-west-1:719261439472:alias/aws/s3",
31+
"environment": [
32+
{
33+
"certificate": "",
34+
"compute_type": "BUILD_GENERAL1_SMALL",
35+
"environment_variable": [],
36+
"image": "aws/codebuild/amazonlinux2-x86_64-standard:2.0",
37+
"image_pull_credentials_type": "CODEBUILD",
38+
"privileged_mode": false,
39+
"registry_credential": [],
40+
"type": "LINUX_CONTAINER"
41+
}
42+
],
43+
"id": "arn:aws:codebuild:eu-west-1:719261439472:project/why-my-project-not-working",
44+
"logs_config": [
45+
{
46+
"cloudwatch_logs": [
47+
{
48+
"group_name": "",
49+
"status": "ENABLED",
50+
"stream_name": ""
51+
}
52+
],
53+
"s3_logs": [
54+
{
55+
"encryption_disabled": false,
56+
"location": "",
57+
"status": "DISABLED"
58+
}
59+
]
60+
}
61+
],
62+
"name": "why-my-project-not-working",
63+
"queued_timeout": 480,
64+
"secondary_artifacts": [],
65+
"secondary_sources": [],
66+
"service_role": "arn:aws:iam::719261439472:role/terra_ci_job",
67+
"source": [
68+
{
69+
"auth": [],
70+
"buildspec": "version: 0.2\nphases:\n install:\n commands:\n - make install_tools\n build:\n commands:\n - make plan_local resource=$TERRA_CI_RESOURCE\nartifacts:\n files:\n - ./tfplan\n name: $TERRA_CI_BUILD_NAME\n",
71+
"git_clone_depth": 1,
72+
"git_submodules_config": [],
73+
"insecure_ssl": false,
74+
"location": "https://github.com/p0tr3c-terraform/terra-ci-single-account.git",
75+
"report_build_status": false,
76+
"type": "GITHUB"
77+
}
78+
],
79+
"source_version": "",
80+
"tags": {},
81+
"vpc_config": []
82+
}
83+
}
84+
},
85+
"data": {}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"resource": {
3+
"aws_codebuild_project": {
4+
"terra_ci": {
5+
"artifacts": [
6+
{
7+
"artifact_identifier": null,
8+
"encryption_disabled": false,
9+
"location": "terra-ci-artifacts-eu-west-1-000002",
10+
"name": null,
11+
"namespace_type": null,
12+
"override_artifact_name": false,
13+
"packaging": null,
14+
"path": null,
15+
"type": "S3"
16+
}
17+
],
18+
"badge_enabled": false,
19+
"build_timeout": 10,
20+
"cache": [],
21+
"description": "Deploy environment configuration",
22+
"environment": [
23+
{
24+
"certificate": null,
25+
"compute_type": "BUILD_GENERAL1_SMALL",
26+
"environment_variable": [],
27+
"image": "aws/codebuild/amazonlinux2-x86_64-standard:2.0",
28+
"image_pull_credentials_type": "CODEBUILD",
29+
"privileged_mode": false,
30+
"registry_credential": [],
31+
"type": "LINUX_CONTAINER"
32+
}
33+
],
34+
"logs_config": [
35+
{
36+
"cloudwatch_logs": [
37+
{
38+
"group_name": null,
39+
"status": "ENABLED",
40+
"stream_name": null
41+
}
42+
],
43+
"s3_logs": [
44+
{
45+
"encryption_disabled": false,
46+
"location": null,
47+
"status": "DISABLED"
48+
}
49+
]
50+
}
51+
],
52+
"name": "terra-ci-runner",
53+
"queued_timeout": 480,
54+
"secondary_artifacts": [],
55+
"secondary_sources": [],
56+
"source": [
57+
{
58+
"auth": [],
59+
"buildspec": "version: 0.2\nphases:\n install:\n commands:\n - make install_tools\n build:\n commands:\n - make plan_local resource=$TERRA_CI_RESOURCE\nartifacts:\n files:\n - ./tfplan\n name: $TERRA_CI_BUILD_NAME\n\n",
60+
"git_clone_depth": 1,
61+
"git_submodules_config": [],
62+
"insecure_ssl": false,
63+
"location": "https://github.com/p0tr3c-terraform/terra-ci-single-account.git",
64+
"report_build_status": false,
65+
"type": "GITHUB"
66+
}
67+
],
68+
"source_version": null,
69+
"tags": null,
70+
"vpc_config": []
71+
}
72+
},
73+
"aws_iam_role": {
74+
"terra_ci_job": {
75+
"assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"codebuild.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n",
76+
"description": null,
77+
"force_detach_policies": false,
78+
"max_session_duration": 3600,
79+
"name": "terra_ci_job",
80+
"name_prefix": null,
81+
"path": "/",
82+
"permissions_boundary": null,
83+
"tags": null
84+
},
85+
"terra_ci_runner": {
86+
"assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"states.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n",
87+
"description": null,
88+
"force_detach_policies": false,
89+
"max_session_duration": 3600,
90+
"name": "terra_ci_runner",
91+
"name_prefix": null,
92+
"path": "/",
93+
"permissions_boundary": null,
94+
"tags": null
95+
}
96+
},
97+
"aws_iam_role_policy": {
98+
"terra_ci_job": {
99+
"name_prefix": null,
100+
"role": "terra_ci_job"
101+
},
102+
"terra_ci_runner": {
103+
"name_prefix": null,
104+
"role": "terra_ci_runner"
105+
}
106+
},
107+
"aws_iam_role_policy_attachment": {
108+
"terra_ci_job_ecr_access": {
109+
"policy_arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser",
110+
"role": "terra_ci_job"
111+
}
112+
},
113+
"aws_s3_bucket": {
114+
"terra_ci": {
115+
"acl": "private",
116+
"bucket": "terra-ci-artifacts-eu-west-1-000002",
117+
"bucket_prefix": null,
118+
"cors_rule": [],
119+
"force_destroy": false,
120+
"grant": [],
121+
"lifecycle_rule": [],
122+
"logging": [],
123+
"object_lock_configuration": [],
124+
"policy": null,
125+
"replication_configuration": [],
126+
"server_side_encryption_configuration": [
127+
{
128+
"rule": [
129+
{
130+
"apply_server_side_encryption_by_default": [
131+
{
132+
"kms_master_key_id": null,
133+
"sse_algorithm": "aws:kms"
134+
}
135+
],
136+
"bucket_key_enabled": false
137+
}
138+
]
139+
}
140+
],
141+
"tags": null,
142+
"website": []
143+
}
144+
},
145+
"aws_sfn_state_machine": {
146+
"terra_ci_runner": {
147+
"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",
148+
"name": "terra-ci-runner",
149+
"tags": null,
150+
"type": "STANDARD"
151+
}
152+
}
153+
},
154+
"data": {}
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"resource": {},
3+
"data": {}
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
{
2+
"resource": {
3+
"aws_codebuild_project": {
4+
"terra_ci": {
5+
"arn": "arn:aws:codebuild:eu-west-1:719261439472:project/terra-ci-runner",
6+
"artifacts": [
7+
{
8+
"artifact_identifier": "",
9+
"encryption_disabled": false,
10+
"location": "terra-ci-artifacts-eu-west-1-000002",
11+
"name": "terra-ci-runner",
12+
"namespace_type": "NONE",
13+
"override_artifact_name": false,
14+
"packaging": "NONE",
15+
"path": "",
16+
"type": "S3"
17+
}
18+
],
19+
"badge_enabled": false,
20+
"badge_url": "",
21+
"build_timeout": 10,
22+
"cache": [
23+
{
24+
"location": "",
25+
"modes": [],
26+
"type": "NO_CACHE"
27+
}
28+
],
29+
"description": "Deploy environment configuration",
30+
"encryption_key": "arn:aws:kms:eu-west-1:719261439472:alias/aws/s3",
31+
"environment": [
32+
{
33+
"certificate": "",
34+
"compute_type": "BUILD_GENERAL1_SMALL",
35+
"environment_variable": [],
36+
"image": "aws/codebuild/amazonlinux2-x86_64-standard:2.0",
37+
"image_pull_credentials_type": "CODEBUILD",
38+
"privileged_mode": false,
39+
"registry_credential": [],
40+
"type": "LINUX_CONTAINER"
41+
}
42+
],
43+
"id": "arn:aws:codebuild:eu-west-1:719261439472:project/terra-ci-runner",
44+
"logs_config": [
45+
{
46+
"cloudwatch_logs": [
47+
{
48+
"group_name": "",
49+
"status": "ENABLED",
50+
"stream_name": ""
51+
}
52+
],
53+
"s3_logs": [
54+
{
55+
"encryption_disabled": false,
56+
"location": "",
57+
"status": "DISABLED"
58+
}
59+
]
60+
}
61+
],
62+
"name": "terra-ci-runner",
63+
"queued_timeout": 480,
64+
"secondary_artifacts": [],
65+
"secondary_sources": [],
66+
"service_role": "arn:aws:iam::719261439472:role/terra_ci_job",
67+
"source": [
68+
{
69+
"auth": [],
70+
"buildspec": "version: 0.2\nphases:\n install:\n commands:\n - make install_tools\n build:\n commands:\n - make plan_local resource=$TERRA_CI_RESOURCE\nartifacts:\n files:\n - ./tfplan\n name: $TERRA_CI_BUILD_NAME\n\n",
71+
"git_clone_depth": 1,
72+
"git_submodules_config": [],
73+
"insecure_ssl": false,
74+
"location": "https://github.com/p0tr3c-terraform/terra-ci-single-account.git",
75+
"report_build_status": false,
76+
"type": "GITHUB"
77+
}
78+
],
79+
"source_version": "",
80+
"tags": {},
81+
"vpc_config": []
82+
}
83+
},
84+
"aws_iam_role": {
85+
"terra_ci_job": {
86+
"arn": "arn:aws:iam::719261439472:role/terra_ci_job",
87+
"assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"codebuild.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}",
88+
"create_date": "2021-05-01T15:08:15Z",
89+
"description": "",
90+
"force_detach_policies": false,
91+
"id": "terra_ci_job",
92+
"inline_policy": [
93+
{
94+
"name": "terraform-20210501150816628700000001",
95+
"policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": \"sts:AssumeRole\",\n \"Resource\": \"arn:aws:iam::719261439472:role/ci\"\n },\n {\n \"Effect\": \"Allow\",\n \"Resource\": [\n \"*\"\n ],\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\"\n ]\n },\n {\n \"Effect\": \"Allow\",\n \"Resource\": [\n \"arn:aws:s3:::terra-ci-artifacts-eu-west-1-000002\",\n \"arn:aws:s3:::terra-ci-artifacts-eu-west-1-000002/*\"\n ],\n \"Action\": [\n \"s3:ListBucket\",\n \"s3:*Object\"\n ]\n }\n ]\n}\n"
96+
}
97+
],
98+
"managed_policy_arns": [
99+
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser"
100+
],
101+
"max_session_duration": 3600,
102+
"name": "terra_ci_job",
103+
"name_prefix": null,
104+
"path": "/",
105+
"permissions_boundary": null,
106+
"tags": {},
107+
"unique_id": "AROA2O52SSXYL7LBSM733"
108+
},
109+
"terra_ci_runner": {
110+
"arn": "arn:aws:iam::719261439472:role/terra_ci_runner",
111+
"assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"states.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}",
112+
"create_date": "2021-05-01T15:08:15Z",
113+
"description": "",
114+
"force_detach_policies": false,
115+
"id": "terra_ci_runner",
116+
"inline_policy": [
117+
{
118+
"name": "terraform-20210501150825425000000003",
119+
"policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"codebuild:StartBuild\",\n \"codebuild:StopBuild\",\n \"codebuild:BatchGetBuilds\"\n ],\n \"Resource\": [\n \"arn:aws:codebuild:eu-west-1:719261439472:project/terra-ci-runner\"\n ]\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"events:PutTargets\",\n \"events:PutRule\",\n \"events:DescribeRule\"\n ],\n \"Resource\": [\n \"arn:aws:events:eu-west-1:719261439472:rule/StepFunctionsGetEventForCodeBuildStartBuildRule\"\n ]\n }\n ]\n}\n"
120+
}
121+
],
122+
"managed_policy_arns": [],
123+
"max_session_duration": 3600,
124+
"name": "terra_ci_runner",
125+
"name_prefix": null,
126+
"path": "/",
127+
"permissions_boundary": null,
128+
"tags": {},
129+
"unique_id": "AROA2O52SSXYDBYYTG4OB"
130+
}
131+
},
132+
"aws_iam_role_policy": {
133+
"terra_ci_job": {
134+
"id": "terra_ci_job:terraform-20210501150816628700000001",
135+
"name": "terraform-20210501150816628700000001",
136+
"name_prefix": null,
137+
"policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": \"sts:AssumeRole\",\n \"Resource\": \"arn:aws:iam::719261439472:role/ci\"\n },\n {\n \"Effect\": \"Allow\",\n \"Resource\": [\n \"*\"\n ],\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\"\n ]\n },\n {\n \"Effect\": \"Allow\",\n \"Resource\": [\n \"arn:aws:s3:::terra-ci-artifacts-eu-west-1-000002\",\n \"arn:aws:s3:::terra-ci-artifacts-eu-west-1-000002/*\"\n ],\n \"Action\": [\n \"s3:ListBucket\",\n \"s3:*Object\"\n ]\n }\n ]\n}\n",
138+
"role": "terra_ci_job"
139+
},
140+
"terra_ci_runner": {
141+
"id": "terra_ci_runner:terraform-20210501150825425000000003",
142+
"name": "terraform-20210501150825425000000003",
143+
"name_prefix": null,
144+
"policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"codebuild:StartBuild\",\n \"codebuild:StopBuild\",\n \"codebuild:BatchGetBuilds\"\n ],\n \"Resource\": [\n \"arn:aws:codebuild:eu-west-1:719261439472:project/terra-ci-runner\"\n ]\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"events:PutTargets\",\n \"events:PutRule\",\n \"events:DescribeRule\"\n ],\n \"Resource\": [\n \"arn:aws:events:eu-west-1:719261439472:rule/StepFunctionsGetEventForCodeBuildStartBuildRule\"\n ]\n }\n ]\n}\n",
145+
"role": "terra_ci_runner"
146+
}
147+
},
148+
"aws_iam_role_policy_attachment": {
149+
"terra_ci_job_ecr_access": {
150+
"id": "terra_ci_job-20210501150817089800000002",
151+
"policy_arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser",
152+
"role": "terra_ci_job"
153+
}
154+
},
155+
"aws_s3_bucket": {
156+
"terra_ci": {
157+
"acceleration_status": "",
158+
"acl": "private",
159+
"arn": "arn:aws:s3:::terra-ci-artifacts-eu-west-1-000002",
160+
"bucket": "terra-ci-artifacts-eu-west-1-000002",
161+
"bucket_domain_name": "terra-ci-artifacts-eu-west-1-000002.s3.amazonaws.com",
162+
"bucket_prefix": null,
163+
"bucket_regional_domain_name": "terra-ci-artifacts-eu-west-1-000002.s3.eu-west-1.amazonaws.com",
164+
"cors_rule": [],
165+
"force_destroy": false,
166+
"grant": [],
167+
"hosted_zone_id": "Z1BKCTXD74EZPE",
168+
"id": "terra-ci-artifacts-eu-west-1-000002",
169+
"lifecycle_rule": [],
170+
"logging": [],
171+
"object_lock_configuration": [],
172+
"policy": null,
173+
"region": "eu-west-1",
174+
"replication_configuration": [],
175+
"request_payer": "BucketOwner",
176+
"server_side_encryption_configuration": [
177+
{
178+
"rule": [
179+
{
180+
"apply_server_side_encryption_by_default": [
181+
{
182+
"kms_master_key_id": "",
183+
"sse_algorithm": "aws:kms"
184+
}
185+
],
186+
"bucket_key_enabled": false
187+
}
188+
]
189+
}
190+
],
191+
"tags": {},
192+
"versioning": [
193+
{
194+
"enabled": false,
195+
"mfa_delete": false
196+
}
197+
],
198+
"website": [],
199+
"website_domain": null,
200+
"website_endpoint": null
201+
}
202+
},
203+
"aws_sfn_state_machine": {
204+
"terra_ci_runner": {
205+
"arn": "arn:aws:states:eu-west-1:719261439472:stateMachine:terra-ci-runner",
206+
"creation_date": "2021-05-01T15:09:28Z",
207+
"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",
208+
"id": "arn:aws:states:eu-west-1:719261439472:stateMachine:terra-ci-runner",
209+
"logging_configuration": [
210+
{
211+
"include_execution_data": false,
212+
"level": "OFF",
213+
"log_destination": ""
214+
}
215+
],
216+
"name": "terra-ci-runner",
217+
"role_arn": "arn:aws:iam::719261439472:role/terra_ci_runner",
218+
"status": "ACTIVE",
219+
"tags": {},
220+
"type": "STANDARD"
221+
}
222+
}
223+
},
224+
"data": {}
225+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"resource": {
3+
"aws_codebuild_project": {
4+
"some_projed": {
5+
"arn": "arn:aws:codebuild:eu-west-1:719261439472:project/why-my-project-not-working",
6+
"artifacts": [
7+
{
8+
"artifact_identifier": "",
9+
"encryption_disabled": true,
10+
"location": "terra-ci-artifacts-eu-west-1-000002",
11+
"name": "why-my-project-not-working",
12+
"namespace_type": "NONE",
13+
"override_artifact_name": true,
14+
"packaging": "NONE",
15+
"path": "",
16+
"type": "S3"
17+
}
18+
],
19+
"badge_enabled": false,
20+
"badge_url": "",
21+
"build_timeout": 10,
22+
"cache": [
23+
{
24+
"location": "",
25+
"modes": [],
26+
"type": "NO_CACHE"
27+
}
28+
],
29+
"description": "Deploy environment configuration",
30+
"encryption_key": "arn:aws:kms:eu-west-1:719261439472:alias/aws/s3",
31+
"environment": [
32+
{
33+
"certificate": "",
34+
"compute_type": "BUILD_GENERAL1_SMALL",
35+
"environment_variable": [],
36+
"image": "aws/codebuild/amazonlinux2-x86_64-standard:2.0",
37+
"image_pull_credentials_type": "CODEBUILD",
38+
"privileged_mode": false,
39+
"registry_credential": [],
40+
"type": "LINUX_CONTAINER"
41+
}
42+
],
43+
"id": "arn:aws:codebuild:eu-west-1:719261439472:project/why-my-project-not-working",
44+
"logs_config": [
45+
{
46+
"cloudwatch_logs": [
47+
{
48+
"group_name": "",
49+
"status": "ENABLED",
50+
"stream_name": ""
51+
}
52+
],
53+
"s3_logs": [
54+
{
55+
"encryption_disabled": false,
56+
"location": "",
57+
"status": "DISABLED"
58+
}
59+
]
60+
}
61+
],
62+
"name": "why-my-project-not-working",
63+
"queued_timeout": 480,
64+
"secondary_artifacts": [],
65+
"secondary_sources": [],
66+
"service_role": "arn:aws:iam::719261439472:role/terra_ci_job",
67+
"source": [
68+
{
69+
"auth": [],
70+
"buildspec": "version: 0.2\nphases:\n install:\n commands:\n - make install_tools\n build:\n commands:\n - make plan_local resource=$TERRA_CI_RESOURCE\nartifacts:\n files:\n - ./tfplan\n name: $TERRA_CI_BUILD_NAME\n",
71+
"git_clone_depth": 1,
72+
"git_submodules_config": [],
73+
"insecure_ssl": false,
74+
"location": "https://github.com/p0tr3c-terraform/terra-ci-single-account.git",
75+
"report_build_status": false,
76+
"type": "GITHUB"
77+
}
78+
],
79+
"source_version": "",
80+
"tags": {},
81+
"vpc_config": []
82+
}
83+
},
84+
"aws_iam_user": {
85+
"ci": {
86+
"arn": "arn:aws:iam::719261439472:user/ci",
87+
"force_destroy": false,
88+
"id": "ci",
89+
"name": "ci",
90+
"path": "/",
91+
"permissions_boundary": null,
92+
"tags": {},
93+
"unique_id": "AIDA2O52SSXYORYI4EPXD"
94+
}
95+
}
96+
},
97+
"data": {}
98+
}

‎test/fixtures/iac/terraform-plan/tf-plan-create.json

+995
Large diffs are not rendered by default.

‎test/fixtures/iac/terraform-plan/tf-plan-destroy.json

+1,083
Large diffs are not rendered by default.

‎test/fixtures/iac/terraform-plan/tf-plan-no-op.json

+1,635
Large diffs are not rendered by default.

‎test/fixtures/iac/terraform-plan/tf-plan-update.json

+736
Large diffs are not rendered by default.

‎test/fixtures/iac/terraform-plan/tf-plan.json

-345
This file was deleted.

‎test/jest/unit/iac-unit-tests/file-parser.fixtures.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
IacFileParsed,
77
} from '../../../../src/cli/commands/test/iac-local-execution/types';
88
import { MissingRequiredFieldsInKubernetesYamlError } from '../../../../src/cli/commands/test/iac-local-execution/parsers/kubernetes-parser';
9-
import { expectedParsingResultDeltaScan } from './terraform-plan-parser.fixtures';
9+
import {
10+
getExpectedResult,
11+
PlanOutputCase,
12+
} from './terraform-plan-parser.fixtures';
1013

1114
const kubernetesYamlFileContent = `
1215
apiVersion: v1
@@ -163,12 +166,15 @@ resource "aws_security_group" "allow_ssh" {
163166
}`;
164167

165168
const terraformPlanFileContent = fs.readFileSync(
166-
path.resolve(__dirname, '../../../fixtures/iac/terraform-plan/tf-plan.json'),
169+
path.resolve(
170+
__dirname,
171+
'../../../fixtures/iac/terraform-plan/tf-plan-create.json',
172+
),
167173
);
168174

169175
const terraformPlanJson = JSON.parse(terraformPlanFileContent.toString());
170176
const terraformPlanMissingFieldsJson = { ...terraformPlanJson };
171-
delete terraformPlanMissingFieldsJson.planned_values;
177+
delete terraformPlanMissingFieldsJson.resource_changes;
172178
const terraformPlanMissingFieldsFileContent = JSON.stringify(
173179
terraformPlanMissingFieldsJson,
174180
);
@@ -216,7 +222,7 @@ export const expectedTerraformParsingResult: IacFileParsed = {
216222
export const expectedTerraformJsonParsingResult: IacFileParsed = {
217223
...terraformPlanDataStub,
218224
engineType: EngineType.Terraform,
219-
jsonContent: expectedParsingResultDeltaScan,
225+
jsonContent: getExpectedResult(false, PlanOutputCase.Create),
220226
};
221227

222228
const invalidTerraformFileContent = `
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,62 @@
11
/* eslint-disable @typescript-eslint/camelcase */
22
import * as fs from 'fs';
33
import * as path from 'path';
4-
import {
5-
IacFileData,
6-
TerraformPlanJson,
7-
TerraformScanInput,
8-
} from '../../../../src/cli/commands/test/iac-local-execution/types';
9-
10-
const tfPlanFixture = fs.readFileSync(
11-
path.resolve(__dirname, '../../../fixtures/iac/terraform-plan/tf-plan.json'),
12-
);
13-
14-
export const iacFileData: IacFileData = {
15-
fileContent: tfPlanFixture.toString(),
16-
filePath: 'dont-care',
17-
fileType: 'json',
18-
};
19-
20-
export const terraformPlanJson: TerraformPlanJson = JSON.parse(
21-
iacFileData.fileContent,
22-
);
23-
24-
const resource = {
25-
ingress: {
26-
cidr_blocks: ['0.0.0.0/0'],
27-
description: null,
28-
from_port: 0,
29-
ipv6_cidr_blocks: null,
30-
prefix_list_ids: null,
31-
protocol: 'tcp',
32-
self: false,
33-
to_port: 65535,
34-
type: 'ingress',
35-
},
36-
};
37-
export const expectedParsingResultFullScan: TerraformScanInput = {
38-
resource: {
39-
aws_security_group: {
40-
terra_ci_allow_outband: resource,
41-
CHILD_MODULE_terra_ci_allow_outband_0: resource,
42-
},
43-
},
44-
data: {},
45-
};
46-
47-
export const expectedParsingResultDeltaScan: TerraformScanInput = {
48-
resource: {
49-
aws_security_group: {
50-
some_updated_resource: resource,
51-
some_created_resource: resource,
52-
},
53-
// "delete" | "create"
54-
aws_iam_user_group_membership: {
55-
some_delete_create_resource: {
56-
groups: ['organization-administrators'],
57-
user: 'p0tr3c',
58-
},
59-
},
60-
// "create" | "delete"
61-
aws_instance: {
62-
some_create_delete_resource: {
63-
ami: 'ami-0831162e2eb204b16',
64-
associate_public_ip_address: true,
65-
credit_specification: [],
66-
disable_api_termination: null,
67-
ebs_optimized: null,
68-
get_password_data: false,
69-
hibernation: null,
70-
iam_instance_profile: null,
71-
instance_initiated_shutdown_behavior: null,
72-
instance_type: 't3.micro',
73-
key_name: 'terra-ci',
74-
monitoring: null,
75-
source_dest_check: true,
76-
subnet_id: 'subnet-050411f3bb5f9dbe4',
77-
tags: null,
78-
timeouts: null,
79-
user_data: null,
80-
user_data_base64: null,
81-
volume_tags: null,
82-
vpc_security_group_ids: ['sg-0455cfe38c670b7b3'],
83-
},
84-
},
85-
},
86-
data: {},
87-
};
88-
89-
const withoutChildModules = (JSON.parse(
90-
tfPlanFixture.toString(),
91-
) as unknown) as TerraformPlanJson;
92-
delete withoutChildModules.planned_values.root_module.child_modules;
93-
export const iacFileDataNoChildModules: IacFileData = {
94-
fileContent: JSON.stringify(withoutChildModules),
95-
filePath: 'dont-care',
96-
fileType: 'json',
97-
};
98-
99-
export const terraformPlanNoChildModulesJson = JSON.parse(
100-
iacFileDataNoChildModules.fileContent,
101-
);
102-
103-
export const expectedParsingResultWithoutChildModules: TerraformScanInput = {
104-
resource: {
105-
aws_security_group: {
106-
terra_ci_allow_outband: resource,
107-
},
108-
},
109-
data: {},
110-
};
111-
112-
const planWithoutRootModule = (JSON.parse(
113-
tfPlanFixture.toString(),
114-
) as unknown) as TerraformPlanJson;
115-
delete planWithoutRootModule.planned_values;
116-
export const iacFileDataWithoutRootModule: IacFileData = {
117-
fileContent: JSON.stringify(planWithoutRootModule),
118-
filePath: 'dont-care',
119-
fileType: 'json',
120-
};
121-
122-
const planWithoutResourceChanges = (JSON.parse(
123-
tfPlanFixture.toString(),
124-
) as unknown) as TerraformPlanJson;
125-
delete planWithoutResourceChanges.resource_changes;
126-
export const iacFileDataWithoutResourceChanges: IacFileData = {
127-
fileContent: JSON.stringify(planWithoutResourceChanges),
128-
filePath: 'dont-care',
129-
fileType: 'json',
130-
};
4+
import { IacFileData } from '../../../../src/cli/commands/test/iac-local-execution/types';
5+
6+
export enum PlanOutputCase {
7+
Create = 'tf-plan-create', // plan with create actions
8+
Destroy = 'tf-plan-destroy', // plan with destroy actions
9+
NoOp = 'tf-plan-no-op', // plan with no-op actions
10+
Update = 'tf-plan-update', // plan with updates actions
11+
}
12+
13+
export function getTfPlanData(planOutputCase: PlanOutputCase) {
14+
const tfPlanFile = fs.readFileSync(
15+
path.resolve(
16+
__dirname,
17+
`../../../fixtures/iac/terraform-plan/${planOutputCase}.json`,
18+
),
19+
);
20+
21+
const iacFileData: IacFileData = {
22+
fileContent: tfPlanFile.toString(),
23+
filePath: 'dont-care',
24+
fileType: 'json',
25+
};
26+
27+
return iacFileData;
28+
}
29+
30+
export function getExpectedResult(
31+
isFullScan: boolean,
32+
planOutputCase: PlanOutputCase,
33+
) {
34+
const tfPlanExpectedResources = JSON.parse(
35+
fs
36+
.readFileSync(
37+
path.resolve(
38+
__dirname,
39+
`../../../fixtures/iac/terraform-plan/expected-parser-results/${
40+
isFullScan ? 'full-scan' : 'delta-scan'
41+
}/${planOutputCase}.resources.json`,
42+
),
43+
)
44+
.toString(),
45+
);
46+
47+
return tfPlanExpectedResources;
48+
}
49+
50+
// For regression testing
51+
type ScanModeTestCase = [{ isFullScan: boolean }];
52+
type PlanOutputTestCase = [PlanOutputCase];
53+
export const scanModeCases: ScanModeTestCase[] = [
54+
[{ isFullScan: false }],
55+
[{ isFullScan: true }],
56+
];
57+
export const planOutputCases: PlanOutputTestCase[] = [
58+
[PlanOutputCase.Create],
59+
[PlanOutputCase.Destroy],
60+
[PlanOutputCase.NoOp],
61+
[PlanOutputCase.Update],
62+
];
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,60 @@
11
import { tryParsingTerraformPlan } from '../../../../src/cli/commands/test/iac-local-execution/parsers/terraform-plan-parser';
22
import {
3-
iacFileData,
4-
terraformPlanJson,
5-
iacFileDataNoChildModules,
6-
expectedParsingResultFullScan,
7-
expectedParsingResultDeltaScan,
8-
expectedParsingResultWithoutChildModules,
9-
terraformPlanNoChildModulesJson,
3+
getExpectedResult,
4+
getTfPlanData,
5+
scanModeCases,
6+
planOutputCases,
107
} from './terraform-plan-parser.fixtures';
11-
import { EngineType } from '../../../../src/cli/commands/test/iac-local-execution/types';
8+
import {
9+
EngineType,
10+
TerraformPlanJson,
11+
} from '../../../../src/cli/commands/test/iac-local-execution/types';
1212

1313
describe('tryParsingTerraformPlan', () => {
14-
describe('full scan', () => {
15-
it('returns the expected resources', () => {
16-
const parsedTerraformPlan = tryParsingTerraformPlan(
17-
iacFileData,
18-
terraformPlanJson,
19-
{ isFullScan: true },
20-
);
21-
expect(parsedTerraformPlan[0]).toEqual({
22-
...iacFileData,
23-
engineType: EngineType.Terraform,
24-
jsonContent: expectedParsingResultFullScan,
25-
});
26-
});
14+
/*
15+
This are regression tests that iterate on major real terraform plan outputs.
16+
Used Plan cases are:
17+
1. Plan which creates new resources
18+
2. Plan which deletes resources
19+
3. Plan which doesn't do anything
20+
4. Plan which updates resources
21+
These tests validate that the correct resources are being extracted, based on the give scan mode (Full/Delta).
22+
These tests do not cover scanning for finding vulnerabilites, but only for the resource extraction logic.
23+
**/
24+
describe('Parsing regression testing', () => {
25+
describe.each(scanModeCases)('if: %p', (scanOptions) => {
26+
test.each(planOutputCases)(
27+
'for %p, it extracts the expected resources',
28+
(planOutputType) => {
29+
// Arrange
30+
const iacFileData = getTfPlanData(planOutputType);
31+
const expectedResources = getExpectedResult(
32+
scanOptions.isFullScan,
33+
planOutputType,
34+
);
35+
const terraformPlanJson: TerraformPlanJson = JSON.parse(
36+
iacFileData.fileContent,
37+
);
2738

28-
it('does not fail if no child-modules are present', () => {
29-
const parsedTerraformPlan = tryParsingTerraformPlan(
30-
iacFileDataNoChildModules,
31-
terraformPlanNoChildModulesJson,
32-
{ isFullScan: true },
33-
);
34-
expect(parsedTerraformPlan[0]).toEqual({
35-
...iacFileDataNoChildModules,
36-
engineType: EngineType.Terraform,
37-
jsonContent: expectedParsingResultWithoutChildModules,
38-
});
39-
});
40-
});
39+
// Act
40+
const parsedTerraformPlan = tryParsingTerraformPlan(
41+
iacFileData,
42+
terraformPlanJson,
43+
scanOptions,
44+
);
4145

42-
describe('default delta scan', () => {
43-
it('returns the expected resources', () => {
44-
const parsedTerraformPlan = tryParsingTerraformPlan(
45-
iacFileData,
46-
terraformPlanJson,
46+
console.debug(
47+
`scanOptions.isFullScan: ${scanOptions.isFullScan} && planOutputType: ${planOutputType}`,
48+
);
49+
50+
// Assert
51+
expect(parsedTerraformPlan[0]).toEqual({
52+
...iacFileData,
53+
engineType: EngineType.Terraform,
54+
jsonContent: expectedResources,
55+
});
56+
},
4757
);
48-
expect(parsedTerraformPlan[0]).toEqual({
49-
...iacFileData,
50-
engineType: EngineType.Terraform,
51-
jsonContent: expectedParsingResultDeltaScan,
52-
});
5358
});
5459
});
55-
56-
// TODO: add test for extracting a resource from a data input source
57-
// deferred as it required to re-generate a new wasm fixture with a rule that applies to a data-source
5860
});

‎test/smoke/spec/iac/snyk_test_local_exec_spec.sh

+11-11
Original file line numberDiff line numberDiff line change
@@ -223,55 +223,55 @@ Describe "Snyk iac test --experimental command"
223223
# Note that this now defaults to the delta scan, not the full scan.
224224
# in the future a flag will be added to control this functionality.
225225
It "finds issues in a Terraform plan file"
226-
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental
226+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan-create.json --experimental
227227
The status should equal 1 # issues found
228-
The output should include "Testing tf-plan.json"
228+
The output should include "tf-plan-create.json"
229229

230230
# Outputs issues
231231
The output should include "Infrastructure as code issues:"
232232
# Root module
233233
The output should include ""
234234
The output should include " introduced by"
235235

236-
The output should include "tf-plan.json for known issues, found"
236+
The output should include "tf-plan-create.json for known issues, found"
237237
End
238238

239239
It "finds issues in a Terraform plan file - full scan flag"
240-
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan=planned-values
240+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan-create.json --experimental --scan=planned-values
241241
The status should equal 1 # issues found
242-
The output should include "Testing tf-plan.json"
242+
The output should include "Testing tf-plan-create.json"
243243

244244
# Outputs issues
245245
The output should include "Infrastructure as code issues:"
246246
# Root module
247247
The output should include ""
248248
The output should include " introduced by"
249249

250-
The output should include "tf-plan.json for known issues, found"
250+
The output should include "tf-plan-create.json for known issues, found"
251251
End
252252

253253
It "finds issues in a Terraform plan file - explicit delta scan with flag"
254-
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan=resource-changes
254+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan-create.json --experimental --scan=resource-changes
255255
The status should equal 1 # issues found
256-
The output should include "Testing tf-plan.json"
256+
The output should include "Testing tf-plan-create.json"
257257

258258
# Outputs issues
259259
The output should include "Infrastructure as code issues:"
260260
# Root module
261261
The output should include ""
262262
The output should include " introduced by"
263263

264-
The output should include "tf-plan.json for known issues, found"
264+
The output should include "tf-plan-create.json for known issues, found"
265265
End
266266

267267
It "errors when a wrong value is passed to the --scan flag"
268-
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan=rsrc-changes
268+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan-create.json.json --experimental --scan=rsrc-changes
269269
The status should equal 2 # failure
270270
The output should include "Unsupported value"
271271
End
272272

273273
It "errors when no value is provided to the --scan flag"
274-
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan
274+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan-create.json.json --experimental --scan
275275
The status should equal 2 # failure
276276
The output should include "Unsupported value"
277277
End

0 commit comments

Comments
 (0)
Please sign in to comment.