Skip to content

Commit

Permalink
fix: tf plan full scan
Browse files Browse the repository at this point in the history
  • Loading branch information
rontalx committed May 6, 2021
1 parent 7bc9229 commit ac6208d
Show file tree
Hide file tree
Showing 19 changed files with 5,338 additions and 587 deletions.
Expand Up @@ -5,7 +5,8 @@ import {
TerraformPlanJson,
TerraformPlanResource,
ResourceActions,
VALID_RESOURCE_ACTIONS,
VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN,
VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN,
TerraformScanInput,
TerraformPlanResourceChange,
IaCErrorCodes,
Expand Down Expand Up @@ -35,19 +36,26 @@ function terraformPlanReducer(
function resourceChangeReducer(
scanInput: TerraformScanInput,
resource: TerraformPlanResourceChange,
isFullScan: boolean,
): TerraformScanInput {
// TODO: investigate if we need to address also `after_unknown` field.
const { actions, after } = resource.change || { actions: [], after: {} };
if (isValidResourceActions(actions)) {
if (isValidResourceActions(actions, isFullScan)) {
const resourceForReduction = { ...resource, values: after || {} };
return terraformPlanReducer(scanInput, resourceForReduction);
}

return scanInput;
}

function isValidResourceActions(action: ResourceActions): boolean {
return VALID_RESOURCE_ACTIONS.some((validAction: string[]) => {
function isValidResourceActions(
action: ResourceActions,
isFullScan: boolean,
): boolean {
const VALID_ACTIONS = isFullScan
? VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN
: VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN;
return VALID_ACTIONS.some((validAction: string[]) => {
if (action.length !== validAction.length) {
return false;
}
Expand All @@ -57,56 +65,28 @@ function isValidResourceActions(action: ResourceActions): boolean {
});
}

function extractRootModuleResources(
terraformPlanJson: TerraformPlanJson,
): Array<TerraformPlanResource> {
return terraformPlanJson.planned_values?.root_module?.resources || [];
}

function extractChildModulesResources(
terraformPlanJson: TerraformPlanJson,
): Array<TerraformPlanResource> {
const childModules =
terraformPlanJson?.planned_values?.root_module?.child_modules || [];
const extractedChildModuleResources: Array<TerraformPlanResource> = [];
childModules.forEach((childModule) =>
childModule.resources.forEach((resource) =>
extractedChildModuleResources.push(resource),
),
);
return extractedChildModuleResources;
}

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

function extractResourcesForFullScan(
terraformPlanJson: TerraformPlanJson,
): TerraformScanInput {
const rootModuleResources = extractRootModuleResources(terraformPlanJson);
const childModuleResources = extractChildModulesResources(terraformPlanJson);
return [
...rootModuleResources,
...childModuleResources,
].reduce(terraformPlanReducer, { resource: {}, data: {} });
}

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

export function isTerraformPlan(terraformPlanJson: TerraformPlanJson): boolean {
const missingRequiredFields =
!terraformPlanJson.planned_values?.root_module ||
terraformPlanJson.resource_changes === undefined;
return !missingRequiredFields;
}
Expand All @@ -117,14 +97,10 @@ export function tryParsingTerraformPlan(
{ isFullScan }: { isFullScan: boolean } = { isFullScan: false },
): Array<IacFileParsed> {
try {
const scannableInput = isFullScan
? extractResourcesForFullScan(terraformPlanJson)
: extractResourcesForDeltaScan(terraformPlanJson);

return [
{
...terraformPlanFile,
jsonContent: scannableInput,
jsonContent: extractResourcesForScan(terraformPlanJson, isFullScan),
engineType: EngineType.Terraform,
},
];
Expand Down
14 changes: 7 additions & 7 deletions src/cli/commands/test/iac-local-execution/types.ts
Expand Up @@ -170,12 +170,6 @@ export interface TerraformPlanResourceChange

export interface TerraformPlanJson {
// there are more values, but these are the required ones for us to scan
planned_values: {
root_module: {
resources: Array<TerraformPlanResource>;
child_modules: Array<{ resources: Array<TerraformPlanResource> }>;
};
};
resource_changes: Array<TerraformPlanResourceChange>;
}
export interface TerraformScanInput {
Expand All @@ -195,13 +189,19 @@ export type ResourceActions =
| ['delete'];

// we will be scanning the `create` & `update` actions only.
export const VALID_RESOURCE_ACTIONS: ResourceActions[] = [
export const VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN: ResourceActions[] = [
['create'],
['update'],
['create', 'delete'],
['delete', 'create'],
];

// scans all actions including 'no-op' in order to iterate on all resources.
export const VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN: ResourceActions[] = [
['no-op'],
...VALID_RESOURCE_ACTIONS_FOR_DELTA_SCAN,
];

// Error codes used for Analytics & Debugging
// Within a single module, increments are in 1.
// Between modules, increments are in 10, according to the order of execution.
Expand Down
@@ -0,0 +1,155 @@
{
"resource": {
"aws_codebuild_project": {
"terra_ci": {
"artifacts": [
{
"artifact_identifier": null,
"encryption_disabled": false,
"location": "terra-ci-artifacts-eu-west-1-000002",
"name": null,
"namespace_type": null,
"override_artifact_name": false,
"packaging": null,
"path": null,
"type": "S3"
}
],
"badge_enabled": false,
"build_timeout": 10,
"cache": [],
"description": "Deploy environment configuration",
"environment": [
{
"certificate": null,
"compute_type": "BUILD_GENERAL1_SMALL",
"environment_variable": [],
"image": "aws/codebuild/amazonlinux2-x86_64-standard:2.0",
"image_pull_credentials_type": "CODEBUILD",
"privileged_mode": false,
"registry_credential": [],
"type": "LINUX_CONTAINER"
}
],
"logs_config": [
{
"cloudwatch_logs": [
{
"group_name": null,
"status": "ENABLED",
"stream_name": null
}
],
"s3_logs": [
{
"encryption_disabled": false,
"location": null,
"status": "DISABLED"
}
]
}
],
"name": "terra-ci-runner",
"queued_timeout": 480,
"secondary_artifacts": [],
"secondary_sources": [],
"source": [
{
"auth": [],
"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",
"git_clone_depth": 1,
"git_submodules_config": [],
"insecure_ssl": false,
"location": "https://github.com/p0tr3c-terraform/terra-ci-single-account.git",
"report_build_status": false,
"type": "GITHUB"
}
],
"source_version": null,
"tags": null,
"vpc_config": []
}
},
"aws_iam_role": {
"terra_ci_job": {
"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",
"description": null,
"force_detach_policies": false,
"max_session_duration": 3600,
"name": "terra_ci_job",
"name_prefix": null,
"path": "/",
"permissions_boundary": null,
"tags": null
},
"terra_ci_runner": {
"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",
"description": null,
"force_detach_policies": false,
"max_session_duration": 3600,
"name": "terra_ci_runner",
"name_prefix": null,
"path": "/",
"permissions_boundary": null,
"tags": null
}
},
"aws_iam_role_policy": {
"terra_ci_job": {
"name_prefix": null,
"role": "terra_ci_job"
},
"terra_ci_runner": {
"name_prefix": null,
"role": "terra_ci_runner"
}
},
"aws_iam_role_policy_attachment": {
"terra_ci_job_ecr_access": {
"policy_arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser",
"role": "terra_ci_job"
}
},
"aws_s3_bucket": {
"terra_ci": {
"acl": "private",
"bucket": "terra-ci-artifacts-eu-west-1-000002",
"bucket_prefix": null,
"cors_rule": [],
"force_destroy": false,
"grant": [],
"lifecycle_rule": [],
"logging": [],
"object_lock_configuration": [],
"policy": null,
"replication_configuration": [],
"server_side_encryption_configuration": [
{
"rule": [
{
"apply_server_side_encryption_by_default": [
{
"kms_master_key_id": null,
"sse_algorithm": "aws:kms"
}
],
"bucket_key_enabled": false
}
]
}
],
"tags": null,
"website": []
}
},
"aws_sfn_state_machine": {
"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",
"tags": null,
"type": "STANDARD"
}
}
},
"data": {}
}
@@ -0,0 +1,4 @@
{
"resource": {},
"data": {}
}
@@ -0,0 +1,4 @@
{
"resource": {},
"data": {}
}

0 comments on commit ac6208d

Please sign in to comment.