Skip to content

Commit

Permalink
Add tests to module (CON-000) (#9)
Browse files Browse the repository at this point in the history
* Add tests

* commit

* change main

* update docker version

* Fixed a flaky test, added more logger tests, fix typos

* Add file extractors tests

---------

Co-authored-by: Shaked Karta <[email protected]>
  • Loading branch information
greensd4 and shakedkarta authored Mar 25, 2024
1 parent ccc3bda commit f298e52
Show file tree
Hide file tree
Showing 39 changed files with 2,015 additions and 70 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21.8
require (
github.com/anchore/stereoscope v0.0.2-0.20240208195325-681f6715b0e3
github.com/anchore/syft v0.105.0
github.com/saferwall/pe v1.5.2
gopkg.in/yaml.v2 v2.4.0
helm.sh/helm/v3 v3.14.2
)
Expand Down Expand Up @@ -59,7 +60,7 @@ require (
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v25.0.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v25.0.3+incompatible // indirect
github.com/docker/docker v25.0.5+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
Expand Down Expand Up @@ -178,7 +179,6 @@ require (
github.com/rivo/uniseg v0.4.6 // indirect
github.com/rubenv/sql-migrate v1.5.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saferwall/pe v1.5.2 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbT
github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
Expand Down
126 changes: 126 additions & 0 deletions internal/extractors/docker_compose_extractor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package extractors

import (
"github.com/CheckmarxDev/containers-resolver/internal/logger"
"github.com/CheckmarxDev/containers-resolver/internal/types"
"testing"
)

func TestExtractImagesFromDockerComposeFiles(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{
{FullPath: "../../test_files/imageExtraction/dockerCompose/docker-compose.yaml", RelativePath: "docker-compose.yaml"},
{FullPath: "../../test_files/imageExtraction/dockerCompose/docker-compose-2.yaml", RelativePath: "docker-compose-2.yaml"},
}

images, err := ExtractImagesFromDockerComposeFiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

expectedImages := map[string]types.ImageLocation{
"postgres:12.0": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose-2.yaml"},
"minio/minio:RELEASE.2020-06-22T03-12-50Z": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose-2.yaml"},
"redis:6.0.10-alpine": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose-2.yaml"},
"buildimage:latest": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose.yaml"},
}

if len(images) != len(expectedImages) {
t.Errorf("Expected %d images, but got %d", len(expectedImages), len(images))
}

for _, image := range images {
expectedLocation, ok := expectedImages[image.Name]
if !ok {
t.Errorf("Unexpected image found: %s", image.Name)
continue
}

if len(image.ImageLocations) != 1 {
t.Errorf("Expected image %s to have exactly one location, but got %d", image.Name, len(image.ImageLocations))
continue
}

if image.ImageLocations[0].Path != expectedLocation.Path {
t.Errorf("Expected image %s to have path %s, but got %s", image.Name, expectedLocation.Path, image.ImageLocations[0].Path)
}

if image.ImageLocations[0].Origin != expectedLocation.Origin {
t.Errorf("Expected image %s to have origin %s, but got %s", image.Name, expectedLocation.Origin, image.ImageLocations[0].Origin)
}
}
}

func TestExtractImagesFromDockerComposeFile(t *testing.T) {
l := logger.NewLogger(false)
filePath := types.FilePath{FullPath: "../../test_files/imageExtraction/dockerCompose/docker-compose-2.yaml", RelativePath: "docker-compose-2.yaml"}

images, err := extractImagesFromDockerComposeFile(l, filePath)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

expectedImages := map[string]types.ImageLocation{
"postgres:12.0": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose-2.yaml"},
"minio/minio:RELEASE.2020-06-22T03-12-50Z": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose-2.yaml"},
"redis:6.0.10-alpine": {Origin: types.DockerComposeFileOrigin, Path: "docker-compose-2.yaml"},
}

if len(images) != len(expectedImages) {
t.Errorf("Expected %d images, but got %d", len(expectedImages), len(images))
}

for _, image := range images {
expectedLocation, ok := expectedImages[image.Name]
if !ok {
t.Errorf("Unexpected image found: %s", image.Name)
continue
}

if len(image.ImageLocations) != 1 {
t.Errorf("Expected image %s to have exactly one location, but got %d", image.Name, len(image.ImageLocations))
continue
}

if image.ImageLocations[0].Path != expectedLocation.Path {
t.Errorf("Expected image %s to have path %s, but got %s", image.Name, expectedLocation.Path, image.ImageLocations[0].Path)
}

if image.ImageLocations[0].Origin != expectedLocation.Origin {
t.Errorf("Expected image %s to have origin %s, but got %s", image.Name, expectedLocation.Origin, image.ImageLocations[0].Origin)
}
}
}

func TestExtractImagesFromDockerComposeFiles_NoFilesFound(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{} // No files provided

images, err := ExtractImagesFromDockerComposeFiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

if len(images) != 0 {
t.Errorf("Expected 0 images, but got %d", len(images))
}
}

func TestExtractImagesFromDockerComposeFiles_NoImagesFound(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{
{FullPath: "../../test_files/imageExtraction/dockerCompose/docker-compose-5.yaml", RelativePath: "docker-compose-5.yaml"}, // Empty Docker Compose file
}

images, err := ExtractImagesFromDockerComposeFiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

if len(images) != 0 {
t.Errorf("Expected 0 images, but got %d", len(images))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,43 @@ func ExtractImagesFromDockerfiles(logger *logger.Logger, filePaths []types.FileP
return imageNames, nil
}

func extractImagesFromDockerfile(l *logger.Logger, filePath types.FilePath) ([]types.ImageModel, error) {
func extractImagesFromDockerfile(logger *logger.Logger, filePath types.FilePath) ([]types.ImageModel, error) {
var imageNames []types.ImageModel

aliases := make(map[string]string) // Map to store aliases and their corresponding real image names

file, err := os.Open(filePath.FullPath)
if err != nil {
return nil, err
}
defer func(file *os.File) {
err = file.Close()
if err != nil {
l.Warn("Could not close dockerfile: %s err: %+v", file.Name(), err)
logger.Warn("Could not close dockerfile: %s err: %+v", file.Name(), err)
}
}(file)

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()

if match := regexp.MustCompile(`\bFROM\s+([\w./-]+)(?::([\w.-]+))?`).FindStringSubmatch(line); match != nil {
// Check if the line defines an alias
if match := regexp.MustCompile(`^\s*FROM\s+([\w./-]+(?::[\w.-]+)?)(?:\s+AS\s+(\w+))?`).FindStringSubmatch(line); match != nil {
imageName := match[1]
alias := match[2]
if alias != "" {
// Check if the alias points to another alias
realName := resolveAlias(alias, aliases)
if realName != "" {
aliases[alias] = realName // Store the alias and its corresponding real image name
} else {
aliases[alias] = imageName // Store the alias and its corresponding real image name
}
}
}

// Check if the line contains an image reference
if match := regexp.MustCompile(`\bFROM\s+([\w./-]+)(?::([\w.-]+))?\b`).FindStringSubmatch(line); match != nil {
imageName := match[1]
tag := match[2]

Expand All @@ -54,6 +72,13 @@ func extractImagesFromDockerfile(l *logger.Logger, filePath types.FilePath) ([]t
}

fullImageName := fmt.Sprintf("%s:%s", imageName, tag)

if realName, ok := aliases[imageName]; ok {
if realName != imageName {
continue
}
}

imageNames = append(imageNames, types.ImageModel{
Name: fullImageName,
ImageLocations: []types.ImageLocation{
Expand All @@ -73,6 +98,20 @@ func extractImagesFromDockerfile(l *logger.Logger, filePath types.FilePath) ([]t
return imageNames, nil
}

func resolveAlias(alias string, aliases map[string]string) string {
realName, ok := aliases[alias]
if !ok {
return "" // Alias not found
}

// Check if the real name is also an alias, resolve recursively
if resolvedRealName, ok := aliases[realName]; ok {
return resolveAlias(resolvedRealName, aliases)
}

return realName
}

func printFoundImagesInFile(l *logger.Logger, filePath string, imageNames []types.ImageModel) {
if len(imageNames) > 0 {
l.Debug("Successfully found images in file: %s images are: %v\n", filePath, strings.Join(func() []string {
Expand Down
110 changes: 110 additions & 0 deletions internal/extractors/dockerfile_extractor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package extractors

import (
"github.com/CheckmarxDev/containers-resolver/internal/logger"
"github.com/CheckmarxDev/containers-resolver/internal/types"
"testing"
)

func TestExtractImagesFromDockerfiles(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{
{FullPath: "../../test_files/imageExtraction/dockerfiles/Dockerfile", RelativePath: "Dockerfile"},
{FullPath: "../../test_files/imageExtraction/dockerfiles/Dockerfile-2", RelativePath: "Dockerfile-2"},
}

images, err := ExtractImagesFromDockerfiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

expectedImages := map[string]types.ImageLocation{
"mcr.microsoft.com/dotnet/sdk:6.0": {Origin: types.DockerFileOrigin, Path: "Dockerfile"},
"mcr.microsoft.com/dotnet/aspnet:6.0": {Origin: types.DockerFileOrigin, Path: "Dockerfile"},
"nginx:latest": {Origin: types.DockerFileOrigin, Path: "Dockerfile-2"},
"mcr.microsoft.com/dotnet/aspnet:4.0": {Origin: types.DockerFileOrigin, Path: "Dockerfile-2"},
}

checkResult(t, images, expectedImages)
}

func TestExtractImagesFromDockerfiles_NoFilesFound(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{} // No files provided

images, err := ExtractImagesFromDockerfiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

if len(images) != 0 {
t.Errorf("Expected 0 images, but got %d", len(images))
}
}

func TestExtractImagesFromDockerfiles_NoImagesFound(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{
{FullPath: "../../test_files/imageExtraction/dockerfiles/Dockerfile-3", RelativePath: "Dockerfile-3"}, // Empty Dockerfile
}

images, err := ExtractImagesFromDockerfiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

if len(images) != 0 {
t.Errorf("Expected 0 images, but got %d", len(images))
}
}

func TestExtractImagesFromDockerfiles_OneValidOneInvalid(t *testing.T) {
l := logger.NewLogger(false)

filePaths := []types.FilePath{
{FullPath: "../../test_files/imageExtraction/dockerfiles/Dockerfile", RelativePath: "Dockerfile"},
{FullPath: "../../test_files/imageExtraction/dockerfiles/InvalidDockerfile", RelativePath: "InvalidDockerfile"},
}

images, err := ExtractImagesFromDockerfiles(l, filePaths)
if err != nil {
t.Errorf("Error extracting images: %v", err)
}

expectedImages := map[string]types.ImageLocation{
"mcr.microsoft.com/dotnet/sdk:6.0": {Origin: types.DockerFileOrigin, Path: "Dockerfile"},
"mcr.microsoft.com/dotnet/aspnet:6.0": {Origin: types.DockerFileOrigin, Path: "Dockerfile"},
}

checkResult(t, images, expectedImages)
}

func checkResult(t *testing.T, images []types.ImageModel, expectedImages map[string]types.ImageLocation) {
if len(images) != len(expectedImages) {
t.Errorf("Expected %d images, but got %d", len(expectedImages), len(images))
}

for _, image := range images {
expectedLocation, ok := expectedImages[image.Name]
if !ok {
t.Errorf("Unexpected image found: %s", image.Name)
continue
}

if len(image.ImageLocations) != 1 {
t.Errorf("Expected image %s to have exactly one location, but got %d", image.Name, len(image.ImageLocations))
continue
}

if image.ImageLocations[0].Path != expectedLocation.Path {
t.Errorf("Expected image %s to have path %s, but got %s", image.Name, expectedLocation.Path, image.ImageLocations[0].Path)
}

if image.ImageLocations[0].Origin != expectedLocation.Origin {
t.Errorf("Expected image %s to have origin %s, but got %s", image.Name, expectedLocation.Origin, image.ImageLocations[0].Origin)
}
}
}
Loading

0 comments on commit f298e52

Please sign in to comment.