Skip to content

Commit

Permalink
Added option to bypass automatic tagging by adding comment above spec…
Browse files Browse the repository at this point in the history
…ific resources.
  • Loading branch information
chanaMovshowich committed Jun 6, 2024
1 parent c6a0b50 commit 0359fa4
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/common/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func (r *Runner) TagFile(file string) {
logger.Info(fmt.Sprintf("Failed to parse file %v with parser %v", file, reflect.TypeOf(parser)))
continue
}
utils.AppendSkipedByCommentToRunnerSkippedResources(&r.skippedResources)
isFileTaggable := false
for _, block := range blocks {
if r.isSkippedResourceType(block.GetResourceType()) {
Expand Down
17 changes: 16 additions & 1 deletion src/common/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package runner

import (
"fmt"
"github.com/bridgecrewio/yor/src/common/tagging/tags"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/bridgecrewio/yor/src/common/tagging/tags"

cloudformationStructure "github.com/bridgecrewio/yor/src/cloudformation/structure"
"github.com/bridgecrewio/yor/src/common/clioptions"
"github.com/bridgecrewio/yor/src/common/gitservice"
Expand Down Expand Up @@ -221,6 +222,20 @@ func TestRunnerInternals(t *testing.T) {
assert.NotContains(t, output, "aws_s3_bucket.test-bucket")
})

t.Run("Test skip resource by comment", func(t *testing.T) {
options := clioptions.TagOptions{
Directory: "../../../tests/terraform/skipComment/skipOne.tf",
Parsers: []string{"Terraform"},
}
runner := Runner{}
runner.Init(&options)
runner.parsers[0].ParseFile(options.Directory)
utils.AppendSkipedByCommentToRunnerSkippedResources(&runner.skippedResources)
result := make([]string, 0)
result = append(result, "aws_instance.example_instance")
assert.Equal(t, result, runner.skippedResources)
})

t.Run("Test skip resource - cloudformation", func(t *testing.T) {
runner := Runner{}
rootDir := "../../../tests/cloudformation"
Expand Down
6 changes: 6 additions & 0 deletions src/common/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import (

// RemoveGcpInvalidChars Source of regex: https://cloud.google.com/compute/docs/labeling-resources
var RemoveGcpInvalidChars = regexp.MustCompile(`[^\p{Ll}\p{Lo}\p{N}_-]`)
var SkipResourcesByComment = make([]string, 0)

func AppendSkipedByCommentToRunnerSkippedResources(skippedResources *[]string) {
*skippedResources = append(*skippedResources, SkipResourcesByComment...)
SkipResourcesByComment = SkipResourcesByComment[:0]
}

func InSlice[T comparable](elems []T, v T) bool {
for _, s := range elems {
Expand Down
8 changes: 8 additions & 0 deletions src/common/yaml/yaml_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,20 @@ func MapResourcesLineYAML(filePath string, resourceNames []string, resourcesStar
for i, line := range fileLines {
cleanContent := strings.TrimSpace(line)
if strings.HasPrefix(cleanContent, resourcesStartToken+":") {
if strings.ToUpper(strings.TrimSpace(fileLines[i-1])) == "#YOR:SKIPALL" {
utils.SkipResourcesByComment = append(utils.SkipResourcesByComment, resourceNames...)
}
readResources = true
resourcesIndent = countLeadingSpaces(line)
continue
}

if readResources {
if i > 0 {
if strings.ToUpper(strings.TrimSpace(fileLines[i-1])) == "#YOR:SKIP" {
utils.SkipResourcesByComment = append(utils.SkipResourcesByComment, strings.Trim(strings.TrimSpace(line), ":"))
}
}
lineIndent := countLeadingSpaces(line)
if lineIndent <= resourcesIndent && strings.TrimSpace(line) != "" && !strings.Contains(line, "#") {
// No longer inside resources block, get the last line of the previous resource if exists
Expand Down
32 changes: 32 additions & 0 deletions src/common/yaml/yaml_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/bridgecrewio/yor/src/common/structure"
"github.com/bridgecrewio/yor/src/common/tagging/simple"
"github.com/bridgecrewio/yor/src/common/tagging/tags"
"github.com/bridgecrewio/yor/src/common/utils"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -241,3 +242,34 @@ func TestTagReplacement(t *testing.T) {
assert.Equal(t, *res["attribute"], structure.Lines{Start: 40, End: 53})
})
}

func TestYaml_ResorceSkipTagging(t *testing.T) {
t.Run("Test some resources with skip comment added to utils.SkipResourcesByComment", func(t *testing.T) {
filePath := "../../../tests/cloudformation/resources/SkipComment/skipOne.yaml"
resorseSkip := []string{"NewVolume"}
expectedResourceNames := []string{"NewVolume", "NewVolume2"}
MapResourcesLineYAML(filePath, expectedResourceNames, "Resources")
assert.Equal(t, utils.SkipResourcesByComment, resorseSkip)
assert.NotEqual(t, utils.SkipResourcesByComment, "NewVolume2")
defer resetSkipArr()
})
t.Run("All resources with skip comment added to utils.SkipResourcesByComment", func(t *testing.T) {
filePath := "../../../tests/cloudformation/resources/SkipComment/skipAll.yaml"
resorseSkip := []string{"NewVolume", "NewVolume2"}
expectedResourceNames := []string{"NewVolume", "NewVolume2"}
MapResourcesLineYAML(filePath, expectedResourceNames, "Resources")
assert.Equal(t, utils.SkipResourcesByComment, resorseSkip)
defer resetSkipArr()
})
t.Run("No resources with skip all comment in the file, utils.SkipResourcesByComment should be empty", func(t *testing.T) {
filePath := "../../../tests/cloudformation/resources/SkipComment/noSkip.yaml"
expectedResourceNames := []string{"NewVolume"}
MapResourcesLineYAML(filePath, expectedResourceNames, "Resources")
assert.Empty(t, utils.SkipResourcesByComment)
defer resetSkipArr()
})
}

func resetSkipArr() {
utils.SkipResourcesByComment = []string{}
}
13 changes: 13 additions & 0 deletions src/terraform/structure/terraform_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (p *TerraformParser) ParseFile(filePath string) ([]structure.IBlock, error)
if err != nil {
return nil, fmt.Errorf("failed to read file %s because %s", filePath, err)
}
lines := strings.Split(string(src), "\n")

// parse the file into hclwrite.File and hclsyntax.File to allow getting existing tags and lines
hclFile, diagnostics := hclwrite.ParseConfig(src, filePath, hcl.InitialPos)
Expand All @@ -151,6 +152,7 @@ func (p *TerraformParser) ParseFile(filePath string) ([]structure.IBlock, error)

syntaxBlocks := hclSyntaxFile.Body.(*hclsyntax.Body).Blocks

skipAll := false
rawBlocks := hclFile.Body().Blocks()
parsedBlocks := make([]structure.IBlock, 0)
for i, block := range rawBlocks {
Expand All @@ -174,6 +176,17 @@ func (p *TerraformParser) ParseFile(filePath string) ([]structure.IBlock, error)
}
terraformBlock.Init(filePath, block)
terraformBlock.AddHclSyntaxBlock(syntaxBlocks[i])
line := terraformBlock.GetLines().Start
if line > 1 && line <= len(lines) {
lineAbove := lines[line-2]
if strings.ToUpper(strings.TrimSpace(lineAbove))== "#YOR:SKIPALL" {
skipAll = true
}

if strings.ToUpper(strings.TrimSpace(lineAbove)) == "#YOR:SKIP" || skipAll {
utils.SkipResourcesByComment = append(utils.SkipResourcesByComment, terraformBlock.GetResourceID())
}
}
parsedBlocks = append(parsedBlocks, terraformBlock)
}

Expand Down
50 changes: 50 additions & 0 deletions src/terraform/structure/terraform_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,52 @@ import (
"github.com/stretchr/testify/assert"
)

func TestTerraformParser_SkipResourceByComment(t *testing.T) {
t.Run("SkipAll comment added all resources to utils.SkipResourcesByComment", func(t *testing.T) {
// Initialize TerraformParser and parse file with all resources containing skip comment
p := &TerraformParser{}
p.Init("../../../tests/terraform/skipComment/", nil)
defer p.Close()
filePath := "../../../tests/terraform/skipComment/skipAll.tf"
_, err := p.ParseFile(filePath)
if err != nil {
t.Errorf("failed to read hcl file because %s", err)
}
exceptedSkipResources := []string{"aws_vpc.example_vpc", "aws_subnet.example_subnet", "aws_instance.example_instance"}
assert.Equal(t, exceptedSkipResources, utils.SkipResourcesByComment)
defer resetSkipArr()
})

t.Run("No resources with skip comment in the file, utils.SkipResourcesByComment should be empty", func(t *testing.T) {
// Initialize TerraformParser and parse file with no skip tags
p := &TerraformParser{}
p.Init("../../../tests/terraform/skipComment/", nil)
defer p.Close()
filePath := "../../../tests/terraform/skipComment/noSkip.tf"
_, err := p.ParseFile(filePath)
if err != nil {
t.Errorf("failed to read hcl file because %s", err)
}
assert.Empty(t, utils.SkipResourcesByComment)
defer resetSkipArr()
})

t.Run("One resource with skip comment, only that resource added to utils.SkipResourcesByComment", func(t *testing.T) {
// Initialize TerraformParser and parse file with one resource containing skip tag
p := &TerraformParser{}
p.Init("../../../tests/terraform/skipComment/", nil)
defer p.Close()
filePath := "../../../tests/terraform/skipComment/skipOne.tf"
_, err := p.ParseFile(filePath)
if err != nil {
t.Errorf("failed to read hcl file because %s", err)
}
exceptedSkipResources := []string{"aws_instance.example_instance"}
assert.Equal(t, exceptedSkipResources, utils.SkipResourcesByComment)
defer resetSkipArr()
})
}

func TestTerraformParser_ParseFile(t *testing.T) {
t.Run("parse aws eks file", func(t *testing.T) {
p := &TerraformParser{}
Expand Down Expand Up @@ -787,3 +833,7 @@ func compareTokenArrays(got []hclwrite.Tokens, want []hclwrite.Tokens) bool {

return true
}

func resetSkipArr() {
utils.SkipResourcesByComment = []string{}
}
37 changes: 37 additions & 0 deletions tests/cloudformation/resources/skipComment/noSkip.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample EBS Volume with EC2 instance template
Resources:
#YOR:SKIP
NewVolume:
Type: AWS::EC2::Volume
Properties:
Size: 100
Encrypted: true
#Encrypted: false
Tags:
- Key: MyTag
Value: TagValue
- Key: Name
Value: !Ref EnvironmentName
AvailabilityZone: us-west-2a
DeletionPolicy: Snapshot

NewVolume2:
Type: AWS::EC2::Volume
Properties:
Size: 100
Encrypted: true
#Encrypted: false
Tags:
- Key: MyTag
Value: TagValue
- Key: Name
Value: !Ref EnvironmentName
AvailabilityZone: us-west-2a
DeletionPolicy: Snapshot

Outputs:
VolumeId:
Value: !Ref NewVolume
Export:
Name: NewVolumeId
38 changes: 38 additions & 0 deletions tests/cloudformation/resources/skipComment/skipAll.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

AWSTemplateFormatVersion: '2010-09-09'
Description: Sample EBS Volume with EC2 instance template
#yor:skipAll
Resources:
NewVolume:
Type: AWS::EC2::Volume
Properties:
Size: 100
Encrypted: true
#Encrypted: false
Tags:
- Key: MyTag
Value: TagValue
- Key: Name
Value: !Ref EnvironmentName
AvailabilityZone: us-west-2a
DeletionPolicy: Snapshot

NewVolume2:
Type: AWS::EC2::Volume
Properties:
Size: 100
Encrypted: true
#Encrypted: false
Tags:
- Key: MyTag
Value: TagValue
- Key: Name
Value: !Ref EnvironmentName
AvailabilityZone: us-west-2a
DeletionPolicy: Snapshot

Outputs:
VolumeId:
Value: !Ref NewVolume
Export:
Name: NewVolumeId
22 changes: 22 additions & 0 deletions tests/cloudformation/resources/skipComment/skipOne.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample EBS Volume with EC2 instance template
Resources:
NewVolume:
Type: AWS::EC2::Volume
Properties:
Size: 100
Encrypted: true
#Encrypted: false
Tags:
- Key: MyTag
Value: TagValue
- Key: Name
Value: !Ref EnvironmentName
AvailabilityZone: us-west-2a
DeletionPolicy: Snapshot

Outputs:
VolumeId:
Value: !Ref NewVolume
Export:
Name: NewVolumeId
27 changes: 27 additions & 0 deletions tests/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,33 @@ func TestRunResults(t *testing.T) {
}
}
})

t.Run("Test tagging terraform and cloudFormation files and skip resource by comment", func(t *testing.T) {
yorRunner := runner.Runner{}
err := yorRunner.Init(&clioptions.TagOptions{
Directory: ".\\skipComment",
TagGroups: getTagGroups(),
Parsers: []string{"Terraform", "CloudFormation"},
})
tfFileBytes, _ := os.ReadFile("skipComment\\skipResource.tf")
yamlFileBytes, _ := os.ReadFile("skipComment\\skipResource.yaml")
defer func() {
_ = os.WriteFile(".\\skipComment\\skipResource.tf", tfFileBytes, 0644)
_ = os.WriteFile(".\\skipComment\\skipResource.yaml", yamlFileBytes, 0644)
}()
failIfErr(t, err)
reportService, err := yorRunner.TagDirectory()
failIfErr(t, err)

reportService.CreateReport()
report := reportService.GetReport()

newTags := report.NewResourceTags
for _, newTag := range newTags {
assert.NotEqual(t, "aws_vpc.example_vpc", newTag.ResourceID)
assert.NotEqual(t, "NewVolume1", newTag.ResourceID)
}
})
}

func TestTagUncommittedResults(t *testing.T) {
Expand Down
24 changes: 24 additions & 0 deletions tests/integration/skipComment/skipResource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#yor:skip
resource "aws_vpc" "example_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
}
}

resource "aws_subnet" "example_subnet" {
vpc_id = aws_vpc.example_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-1a"
tags = {
yor_name = "example_subnet"
yor_trace = "74091a97-d11a-4500-a0c3-af942a0d8e00"
}
}

resource "aws_instance" "example_instance" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = aws_subnet.example_subnet.id
tags = {
}
}
Loading

0 comments on commit 0359fa4

Please sign in to comment.