github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/containers/podman/podman_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  
    17  package podman_test
    18  
    19  import (
    20  	"os"
    21  	"path/filepath"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/go-cmp/cmp/cmpopts"
    27  	"github.com/google/osv-scalibr/extractor"
    28  	"github.com/google/osv-scalibr/extractor/filesystem"
    29  	"github.com/google/osv-scalibr/extractor/filesystem/containers/podman"
    30  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    31  	scalibrfs "github.com/google/osv-scalibr/fs"
    32  	"github.com/google/osv-scalibr/testing/extracttest"
    33  )
    34  
    35  func TestExtractor_FileRequired(t *testing.T) {
    36  	tests := []struct {
    37  		inputPath string
    38  		want      bool
    39  	}{
    40  		{
    41  			inputPath: "", want: false,
    42  		},
    43  		{
    44  			inputPath: "/home/user/.local/share/containers/storage/db.sql", want: true,
    45  		},
    46  		{
    47  			inputPath: "/home/user/.local/share/containers/storage/libpod/bolt_state.db", want: true,
    48  		},
    49  		{
    50  			inputPath: "/home/user/.local/something.db", want: false,
    51  		},
    52  		{
    53  			inputPath: "/home/user/.local/db.sql", want: false,
    54  		},
    55  	}
    56  	for _, tt := range tests {
    57  		t.Run(tt.inputPath, func(t *testing.T) {
    58  			e := podman.Extractor{}
    59  			got := e.FileRequired(simplefileapi.New(tt.inputPath, nil))
    60  			if got != tt.want {
    61  				t.Errorf("FileRequired(%s) got = %v, want %v", tt.inputPath, got, tt.want)
    62  			}
    63  		})
    64  	}
    65  }
    66  
    67  func TestExtractor_Extract(t *testing.T) {
    68  	// extracttest.TestTableEntry + podman config
    69  	type testTableEntry struct {
    70  		Name         string
    71  		Path         string
    72  		WantPackages []*extractor.Package
    73  		WantErr      error
    74  		Config       podman.Config
    75  	}
    76  
    77  	tests := []testTableEntry{
    78  		{
    79  			// The SQLite driver doesn't fail when opening an improperly formatted file,
    80  			// so the error appears during the container listing phase.
    81  			Name:    "invalid_sqlite_db",
    82  			Path:    "testdata/notdb.sql",
    83  			WantErr: extracttest.ContainsErrStr{Str: "error listing containers in file"},
    84  		},
    85  		{
    86  			Name:    "invalid boltstatedb",
    87  			Path:    "testdata/not_bolt_state.db",
    88  			WantErr: extracttest.ContainsErrStr{Str: "error opening file"},
    89  		},
    90  		{
    91  			Name:   "valid using sqlite3 - all",
    92  			Path:   "testdata/db.sql",
    93  			Config: podman.Config{IncludeStopped: true},
    94  			WantPackages: []*extractor.Package{
    95  				{
    96  					Name:    "docker.io/hello-world",
    97  					Version: "f1f77a0f96b7251d7ef5472705624e2d76db64855b5b121e1cbefe9dc52d0f86",
    98  					Metadata: &podman.Metadata{
    99  						Status: "exited",
   100  						Exited: true,
   101  					},
   102  					Locations: []string{"db.sql"},
   103  				},
   104  				{
   105  					Name:    "postgres",
   106  					Version: "e92968df83750a723114bf998e3e323dda53e4c5c3ea42b22dd6ad6e3df80ca5",
   107  					Metadata: &podman.Metadata{
   108  						ExposedPorts: map[uint16][]string{5432: {"tcp"}},
   109  						PID:          37461,
   110  						Status:       "running",
   111  					},
   112  					Locations: []string{"db.sql"},
   113  				},
   114  				{
   115  					Name:    "redis",
   116  					Version: "a8036f14f15ead9517115576fb4462894a000620c2be556410f6c24afb8a482b",
   117  					Metadata: &podman.Metadata{
   118  						ExposedPorts: map[uint16][]string{6379: {"tcp"}},
   119  						PID:          37379,
   120  						Status:       "running",
   121  					},
   122  					Locations: []string{"db.sql"},
   123  				},
   124  			},
   125  		},
   126  		{
   127  			Name: "valid using sqlite3 - running",
   128  			Path: "testdata/db.sql",
   129  			WantPackages: []*extractor.Package{
   130  				{
   131  					Name:    "postgres",
   132  					Version: "e92968df83750a723114bf998e3e323dda53e4c5c3ea42b22dd6ad6e3df80ca5",
   133  					Metadata: &podman.Metadata{
   134  						ExposedPorts: map[uint16][]string{5432: {"tcp"}},
   135  						PID:          37461,
   136  						Status:       "running",
   137  					},
   138  					Locations: []string{"db.sql"},
   139  				},
   140  				{
   141  					Name:    "redis",
   142  					Version: "a8036f14f15ead9517115576fb4462894a000620c2be556410f6c24afb8a482b",
   143  					Metadata: &podman.Metadata{
   144  						ExposedPorts: map[uint16][]string{6379: {"tcp"}},
   145  						PID:          37379,
   146  						Status:       "running",
   147  					},
   148  					Locations: []string{"db.sql"},
   149  				},
   150  			},
   151  		},
   152  		{
   153  			Name:   "valid using bolt - all",
   154  			Path:   "testdata/bolt_state.db",
   155  			Config: podman.Config{IncludeStopped: true},
   156  			WantPackages: []*extractor.Package{
   157  				{
   158  					Name:    "docker.io/hello-world",
   159  					Version: "f1f77a0f96b7251d7ef5472705624e2d76db64855b5b121e1cbefe9dc52d0f86",
   160  					Metadata: &podman.Metadata{
   161  						Status: "exited",
   162  						Exited: true,
   163  					},
   164  					Locations: []string{"bolt_state.db"},
   165  				},
   166  				{
   167  					Name:    "docker.io/redis",
   168  					Version: "a8036f14f15ead9517115576fb4462894a000620c2be556410f6c24afb8a482b",
   169  					Metadata: &podman.Metadata{
   170  						ExposedPorts: map[uint16][]string{6379: {"tcp"}},
   171  						PID:          4232,
   172  						Status:       "running",
   173  					},
   174  					Locations: []string{"bolt_state.db"},
   175  				},
   176  			},
   177  		},
   178  		{
   179  			Name: "valid using bolt",
   180  			Path: "testdata/bolt_state.db",
   181  			WantPackages: []*extractor.Package{
   182  				{
   183  					Name:    "docker.io/redis",
   184  					Version: "a8036f14f15ead9517115576fb4462894a000620c2be556410f6c24afb8a482b",
   185  					Metadata: &podman.Metadata{
   186  						ExposedPorts: map[uint16][]string{6379: {"tcp"}},
   187  						PID:          4232,
   188  						Status:       "running",
   189  					},
   190  					Locations: []string{"bolt_state.db"},
   191  				},
   192  			},
   193  		},
   194  	}
   195  
   196  	for _, tt := range tests {
   197  		t.Run(tt.Name, func(t *testing.T) {
   198  			extr := podman.New(tt.Config)
   199  
   200  			// Move input to a tmp dir that SCALIBR has write access to
   201  			// (needed for bolt files which are opened using ).
   202  			d := t.TempDir()
   203  			dst := filepath.Join(d, filepath.Base(tt.Path))
   204  			data, err := os.ReadFile(tt.Path)
   205  			if err != nil {
   206  				t.Fatalf("os.ReadFile(%q): %v", tt.Path, err)
   207  			}
   208  			err = os.WriteFile(dst, data, 0644)
   209  			if err != nil {
   210  				t.Fatalf("os.WriteFile(%q): %v", dst, err)
   211  			}
   212  
   213  			scanInput := &filesystem.ScanInput{
   214  				FS: scalibrfs.DirFS(d), Path: filepath.Base(tt.Path), Reader: nil, Root: d, Info: nil,
   215  			}
   216  
   217  			got, err := extr.Extract(t.Context(), scanInput)
   218  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   219  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.Path, diff)
   220  				return
   221  			}
   222  
   223  			opts := []cmp.Option{cmpopts.SortSlices(extracttest.PackageCmpLess), cmpopts.IgnoreTypes(time.Time{})}
   224  			if diff := cmp.Diff(tt.WantPackages, got.Packages, opts...); diff != "" {
   225  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.Path, diff)
   226  			}
   227  		})
   228  	}
   229  }