github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/source/stereoscopesource/image_source_test.go (about)

     1  package stereoscopesource
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/stereoscope"
    14  	"github.com/anchore/stereoscope/pkg/imagetest"
    15  	"github.com/anchore/syft/syft/artifact"
    16  	"github.com/anchore/syft/syft/internal/testutil"
    17  	"github.com/anchore/syft/syft/source"
    18  )
    19  
    20  func Test_StereoscopeImage_Exclusions(t *testing.T) {
    21  	testutil.Chdir(t, "..") // run with source/test-fixtures
    22  
    23  	testCases := []struct {
    24  		desc       string
    25  		input      string
    26  		glob       string
    27  		expected   int
    28  		exclusions []string
    29  	}{
    30  		// NOTE: in the Dockerfile, /target is moved to /, which makes /really a top-level dir
    31  		{
    32  			input:      "image-simple",
    33  			desc:       "a single path excluded",
    34  			glob:       "**",
    35  			expected:   2,
    36  			exclusions: []string{"/really/**"},
    37  		},
    38  		{
    39  			input:      "image-simple",
    40  			desc:       "a directly referenced directory is excluded",
    41  			glob:       "**",
    42  			expected:   2,
    43  			exclusions: []string{"/really"},
    44  		},
    45  		{
    46  			input:      "image-simple",
    47  			desc:       "a partial directory is not excluded",
    48  			glob:       "**",
    49  			expected:   3,
    50  			exclusions: []string{"/reall"},
    51  		},
    52  		{
    53  			input:      "image-simple",
    54  			desc:       "exclude files deeper",
    55  			glob:       "**",
    56  			expected:   2,
    57  			exclusions: []string{"**/nested/**"},
    58  		},
    59  		{
    60  			input:      "image-simple",
    61  			desc:       "files excluded with extension",
    62  			glob:       "**",
    63  			expected:   2,
    64  			exclusions: []string{"**/*1.txt"},
    65  		},
    66  		{
    67  			input:      "image-simple",
    68  			desc:       "keep files with different extensions",
    69  			glob:       "**",
    70  			expected:   3,
    71  			exclusions: []string{"**/target/**/*.jar"},
    72  		},
    73  		{
    74  			input:      "image-simple",
    75  			desc:       "file directly excluded",
    76  			glob:       "**",
    77  			expected:   2,
    78  			exclusions: []string{"**/somefile-1.txt"}, // file-1 renamed to somefile-1 in Dockerfile
    79  		},
    80  	}
    81  
    82  	for _, test := range testCases {
    83  		t.Run(test.desc, func(t *testing.T) {
    84  			imageName := strings.SplitN(imagetest.PrepareFixtureImage(t, "docker-archive", test.input), ":", 2)[1]
    85  
    86  			img, err := stereoscope.GetImage(context.TODO(), imageName)
    87  			require.NoError(t, err)
    88  			require.NotNil(t, img)
    89  
    90  			src := New(
    91  				img,
    92  				ImageConfig{
    93  					Reference: imageName,
    94  					Exclude: source.ExcludeConfig{
    95  						Paths: test.exclusions,
    96  					},
    97  				},
    98  			)
    99  
   100  			t.Cleanup(func() {
   101  				require.NoError(t, src.Close())
   102  			})
   103  
   104  			res, err := src.FileResolver(source.SquashedScope)
   105  			require.NoError(t, err)
   106  
   107  			contents, err := res.FilesByGlob(test.glob)
   108  			require.NoError(t, err)
   109  
   110  			assert.Len(t, contents, test.expected)
   111  		})
   112  	}
   113  }
   114  
   115  func Test_StereoscopeImageSource_ID(t *testing.T) {
   116  	tests := []struct {
   117  		name     string
   118  		alias    source.Alias
   119  		metadata source.ImageMetadata
   120  		want     artifact.ID
   121  	}{
   122  		{
   123  			name: "use raw manifest over chain ID or user input",
   124  			metadata: source.ImageMetadata{
   125  				UserInput: "user-input",
   126  				Layers: []source.LayerMetadata{
   127  					{
   128  						Digest: "a",
   129  					},
   130  					{
   131  						Digest: "b",
   132  					},
   133  					{
   134  						Digest: "c",
   135  					},
   136  				},
   137  				RawManifest: []byte("raw-manifest"),
   138  			},
   139  			want: func() artifact.ID {
   140  				hasher := sha256.New()
   141  				hasher.Write([]byte("raw-manifest"))
   142  				return artifact.ID(fmt.Sprintf("%x", hasher.Sum(nil)))
   143  			}(),
   144  		},
   145  		{
   146  			name: "use chain ID over user input",
   147  			metadata: source.ImageMetadata{
   148  				//UserInput: "user-input",
   149  				Layers: []source.LayerMetadata{
   150  					{
   151  						Digest: "a",
   152  					},
   153  					{
   154  						Digest: "b",
   155  					},
   156  					{
   157  						Digest: "c",
   158  					},
   159  				},
   160  			},
   161  			want: func() artifact.ID {
   162  				metadata := []source.LayerMetadata{
   163  					{
   164  						Digest: "a",
   165  					},
   166  					{
   167  						Digest: "b",
   168  					},
   169  					{
   170  						Digest: "c",
   171  					},
   172  				}
   173  				return artifact.ID(strings.TrimPrefix(calculateChainID(metadata), "sha256:"))
   174  			}(),
   175  		},
   176  		{
   177  			name: "use user input last",
   178  			metadata: source.ImageMetadata{
   179  				UserInput: "user-input",
   180  			},
   181  			want: func() artifact.ID {
   182  				hasher := sha256.New()
   183  				hasher.Write([]byte("user-input"))
   184  				return artifact.ID(fmt.Sprintf("%x", hasher.Sum(nil)))
   185  			}(),
   186  		},
   187  		{
   188  			name: "without alias (first)",
   189  			metadata: source.ImageMetadata{
   190  				UserInput: "user-input",
   191  				Layers: []source.LayerMetadata{
   192  					{
   193  						Digest: "a",
   194  					},
   195  					{
   196  						Digest: "b",
   197  					},
   198  					{
   199  						Digest: "c",
   200  					},
   201  				},
   202  				RawManifest: []byte("raw-manifest"),
   203  			},
   204  			want: "85298926ecd92ed57688f13039017160cd728f04dd0d2d10a10629007106f107",
   205  		},
   206  		{
   207  			name: "always consider alias (first)",
   208  			alias: source.Alias{
   209  				Name:    "alias",
   210  				Version: "version",
   211  			},
   212  			metadata: source.ImageMetadata{
   213  				UserInput: "user-input",
   214  				Layers: []source.LayerMetadata{
   215  					{
   216  						Digest: "a",
   217  					},
   218  					{
   219  						Digest: "b",
   220  					},
   221  					{
   222  						Digest: "c",
   223  					},
   224  				},
   225  				RawManifest: []byte("raw-manifest"),
   226  			},
   227  			want: "a8717e42449960c1dd4963f2f22bd69c7c105e7e82445be0a65aa1825d62ff0d",
   228  		},
   229  		{
   230  			name: "without alias (last)",
   231  			metadata: source.ImageMetadata{
   232  				UserInput: "user-input",
   233  			},
   234  			want: "ab0dff627d80b9753193d7280bec8f45e8ec6b4cb0912c6fffcf7cd782d9739e",
   235  		},
   236  		{
   237  			name: "always consider alias (last)",
   238  			alias: source.Alias{
   239  				Name:    "alias",
   240  				Version: "version",
   241  			},
   242  			metadata: source.ImageMetadata{
   243  				UserInput: "user-input",
   244  			},
   245  			want: "fe86c0eecd5654d3c0c0b2176aa394aef6440347c241aa8d9b628dfdde4287cf",
   246  		},
   247  	}
   248  	for _, tt := range tests {
   249  		t.Run(tt.name, func(t *testing.T) {
   250  			assert.Equal(t, tt.want, deriveIDFromStereoscopeImage(tt.alias, tt.metadata))
   251  		})
   252  	}
   253  }
   254  
   255  func Test_Describe(t *testing.T) {
   256  	tests := []struct {
   257  		name     string
   258  		source   stereoscopeImageSource
   259  		expected source.Description
   260  	}{
   261  		{
   262  			name: "name from user input",
   263  			source: stereoscopeImageSource{
   264  				id: "some-id",
   265  				metadata: source.ImageMetadata{
   266  					UserInput: "user input",
   267  				},
   268  			},
   269  			expected: source.Description{
   270  				ID:   "some-id",
   271  				Name: "user input",
   272  			},
   273  		},
   274  	}
   275  
   276  	for _, test := range tests {
   277  		got := test.source.Describe()
   278  		got.Metadata = nil // might want to test this, but do not to determine if the user input is userd
   279  		require.Equal(t, test.expected, got)
   280  	}
   281  }