github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/test/cli/packages_cmd_test.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "testing" 7 ) 8 9 const ( 10 // this is the number of packages that should be found in the image-pkg-coverage fixture image 11 // when analyzed with the squashed scope. 12 coverageImageSquashedPackageCount = 24 13 ) 14 15 func TestPackagesCmdFlags(t *testing.T) { 16 hiddenPackagesImage := "docker-archive:" + getFixtureImage(t, "image-hidden-packages") 17 coverageImage := "docker-archive:" + getFixtureImage(t, "image-pkg-coverage") 18 nodeBinaryImage := "docker-archive:" + getFixtureImage(t, "image-node-binary") 19 // badBinariesImage := "docker-archive:" + getFixtureImage(t, "image-bad-binaries") 20 tmp := t.TempDir() + "/" 21 22 tests := []struct { 23 name string 24 args []string 25 env map[string]string 26 assertions []traitAssertion 27 }{ 28 { 29 name: "no-args-shows-help", 30 args: []string{"packages"}, 31 assertions: []traitAssertion{ 32 assertInOutput("an image/directory argument is required"), // specific error that should be shown 33 assertInOutput("Generate a packaged-based Software Bill Of Materials"), // excerpt from help description 34 assertFailingReturnCode, 35 }, 36 }, 37 { 38 name: "json-output-flag", 39 args: []string{"packages", "-o", "json", coverageImage}, 40 assertions: []traitAssertion{ 41 assertJsonReport, 42 assertSuccessfulReturnCode, 43 }, 44 }, 45 { 46 name: "quiet-flag-with-logger", 47 args: []string{"packages", "-qvv", "-o", "json", coverageImage}, 48 assertions: []traitAssertion{ 49 assertJsonReport, 50 assertNoStderr, 51 assertSuccessfulReturnCode, 52 }, 53 }, 54 { 55 name: "quiet-flag-with-tui", 56 args: []string{"packages", "-q", "-o", "json", coverageImage}, 57 assertions: []traitAssertion{ 58 assertJsonReport, 59 assertNoStderr, 60 assertSuccessfulReturnCode, 61 }, 62 }, 63 { 64 name: "multiple-output-flags", 65 args: []string{"packages", "-o", "table", "-o", "json=" + tmp + ".tmp/multiple-output-flag-test.json", coverageImage}, 66 assertions: []traitAssertion{ 67 assertTableReport, 68 assertFileExists(tmp + ".tmp/multiple-output-flag-test.json"), 69 assertSuccessfulReturnCode, 70 }, 71 }, 72 // I haven't been able to reproduce locally yet, but in CI this has proven to be unstable: 73 // For the same commit: 74 // pass: https://github.com/anchore/syft/runs/4611344142?check_suite_focus=true 75 // fail: https://github.com/anchore/syft/runs/4611343586?check_suite_focus=true 76 // For the meantime this test will be commented out, but should be added back in as soon as possible. 77 // 78 // { 79 // name: "regression-survive-bad-binaries", 80 // // this image has all sorts of rich binaries from the clang-13 test suite that should do pretty bad things 81 // // to the go cataloger binary path. We should NEVER let a panic stop the cataloging process for these 82 // // specific cases. 83 // 84 // // this is more of an integration test, however, to assert the output we want to see from the application 85 // // a CLI test is much easier. 86 // args: []string{"packages", "-vv", badBinariesImage}, 87 // assertions: []traitAssertion{ 88 // assertInOutput("could not parse possible go binary"), 89 // assertSuccessfulReturnCode, 90 // }, 91 // }, 92 { 93 name: "output-env-binding", 94 env: map[string]string{ 95 "SYFT_OUTPUT": "json", 96 }, 97 args: []string{"packages", coverageImage}, 98 assertions: []traitAssertion{ 99 assertJsonReport, 100 assertSuccessfulReturnCode, 101 }, 102 }, 103 { 104 name: "table-output-flag", 105 args: []string{"packages", "-o", "table", coverageImage}, 106 assertions: []traitAssertion{ 107 assertTableReport, 108 assertSuccessfulReturnCode, 109 }, 110 }, 111 { 112 name: "default-output-flag", 113 args: []string{"packages", coverageImage}, 114 assertions: []traitAssertion{ 115 assertTableReport, 116 assertSuccessfulReturnCode, 117 }, 118 }, 119 { 120 name: "squashed-scope-flag", 121 args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage}, 122 assertions: []traitAssertion{ 123 assertPackageCount(coverageImageSquashedPackageCount), 124 assertSuccessfulReturnCode, 125 }, 126 }, 127 { 128 name: "squashed-scope-flag-hidden-packages", 129 args: []string{"packages", "-o", "json", "-s", "squashed", hiddenPackagesImage}, 130 assertions: []traitAssertion{ 131 assertPackageCount(162), 132 assertNotInOutput("vsftpd"), // hidden package 133 assertSuccessfulReturnCode, 134 }, 135 }, 136 { 137 name: "all-layers-scope-flag", 138 args: []string{"packages", "-o", "json", "-s", "all-layers", hiddenPackagesImage}, 139 assertions: []traitAssertion{ 140 assertPackageCount(163), // packages are now deduplicated for this case 141 assertInOutput("all-layers"), 142 assertInOutput("vsftpd"), // hidden package 143 assertSuccessfulReturnCode, 144 }, 145 }, 146 { 147 name: "all-layers-scope-flag-by-env", 148 args: []string{"packages", "-o", "json", hiddenPackagesImage}, 149 env: map[string]string{ 150 "SYFT_PACKAGE_CATALOGER_SCOPE": "all-layers", 151 }, 152 assertions: []traitAssertion{ 153 assertPackageCount(163), // packages are now deduplicated for this case 154 assertInOutput("all-layers"), 155 assertInOutput("vsftpd"), // hidden package 156 assertSuccessfulReturnCode, 157 }, 158 }, 159 { 160 // we want to make certain that syft can catalog a single go binary and get a SBOM report that is not empty 161 name: "catalog-single-go-binary", 162 args: []string{"packages", "-o", "json", getSyftBinaryLocation(t)}, 163 assertions: []traitAssertion{ 164 assertJsonReport, 165 assertStdoutLengthGreaterThan(1000), 166 assertSuccessfulReturnCode, 167 }, 168 }, 169 { 170 name: "catalog-node-js-binary", 171 args: []string{"packages", "-o", "json", nodeBinaryImage}, 172 assertions: []traitAssertion{ 173 assertJsonReport, 174 assertInOutput("node.js"), 175 assertSuccessfulReturnCode, 176 }, 177 }, 178 { 179 name: "responds-to-package-cataloger-search-options", 180 args: []string{"--help"}, 181 env: map[string]string{ 182 "SYFT_PACKAGE_SEARCH_UNINDEXED_ARCHIVES": "true", 183 "SYFT_PACKAGE_SEARCH_INDEXED_ARCHIVES": "false", 184 }, 185 assertions: []traitAssertion{ 186 // the application config in the log matches that of what we expect to have been configured. Note: 187 // we are not testing further wiring of this option, only that the config responds to 188 // package-cataloger-level options. 189 assertInOutput("search-unindexed-archives: true"), 190 assertInOutput("search-indexed-archives: false"), 191 }, 192 }, 193 { 194 name: "platform-option-wired-up", 195 args: []string{"packages", "--platform", "arm64", "-o", "json", "registry:busybox:1.31"}, 196 assertions: []traitAssertion{ 197 assertInOutput("sha256:1ee006886991ad4689838d3a288e0dd3fd29b70e276622f16b67a8922831a853"), // linux/arm64 image digest 198 assertSuccessfulReturnCode, 199 }, 200 }, 201 { 202 name: "json-file-flag", 203 args: []string{"packages", "-o", "json", "--file", filepath.Join(tmp, "output-1.json"), coverageImage}, 204 assertions: []traitAssertion{ 205 assertSuccessfulReturnCode, 206 assertFileOutput(t, filepath.Join(tmp, "output-1.json"), 207 assertJsonReport, 208 ), 209 }, 210 }, 211 { 212 name: "json-output-flag-to-file", 213 args: []string{"packages", "-o", fmt.Sprintf("json=%s", filepath.Join(tmp, "output-2.json")), coverageImage}, 214 assertions: []traitAssertion{ 215 assertSuccessfulReturnCode, 216 assertFileOutput(t, filepath.Join(tmp, "output-2.json"), 217 assertJsonReport, 218 ), 219 }, 220 }, 221 { 222 name: "catalogers-option", 223 // This will detect enable python-index-cataloger, python-package-cataloger and ruby-gemspec cataloger 224 args: []string{"packages", "-o", "json", "--catalogers", "python,ruby-gemspec", coverageImage}, 225 assertions: []traitAssertion{ 226 assertPackageCount(13), 227 assertSuccessfulReturnCode, 228 }, 229 }, 230 { 231 name: "override-default-parallelism", 232 args: []string{"packages", "-vvv", "-o", "json", coverageImage}, 233 env: map[string]string{ 234 "SYFT_PARALLELISM": "2", 235 }, 236 assertions: []traitAssertion{ 237 // the application config in the log matches that of what we expect to have been configured. 238 assertInOutput("parallelism: 2"), 239 assertInOutput("parallelism=2"), 240 assertPackageCount(coverageImageSquashedPackageCount), 241 assertSuccessfulReturnCode, 242 }, 243 }, 244 { 245 name: "default-parallelism", 246 args: []string{"packages", "-vvv", "-o", "json", coverageImage}, 247 assertions: []traitAssertion{ 248 // the application config in the log matches that of what we expect to have been configured. 249 assertInOutput("parallelism: 1"), 250 assertInOutput("parallelism=1"), 251 assertPackageCount(coverageImageSquashedPackageCount), 252 assertSuccessfulReturnCode, 253 }, 254 }, 255 { 256 name: "password and key not in config output", 257 args: []string{"packages", "-vvv", "-o", "json", coverageImage}, 258 env: map[string]string{ 259 "SYFT_ATTEST_PASSWORD": "secret_password", 260 "SYFT_ATTEST_KEY": "secret_key_path", 261 }, 262 assertions: []traitAssertion{ 263 assertNotInOutput("secret_password"), 264 assertNotInOutput("secret_key_path"), 265 assertPackageCount(coverageImageSquashedPackageCount), 266 assertSuccessfulReturnCode, 267 }, 268 }, 269 } 270 271 for _, test := range tests { 272 t.Run(test.name, func(t *testing.T) { 273 cmd, stdout, stderr := runSyft(t, test.env, test.args...) 274 for _, traitFn := range test.assertions { 275 traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) 276 } 277 logOutputOnFailure(t, cmd, stdout, stderr) 278 }) 279 } 280 } 281 282 func TestRegistryAuth(t *testing.T) { 283 host := "localhost:17" 284 image := fmt.Sprintf("%s/something:latest", host) 285 args := []string{"packages", "-vvv", fmt.Sprintf("registry:%s", image)} 286 287 tests := []struct { 288 name string 289 args []string 290 env map[string]string 291 assertions []traitAssertion 292 }{ 293 { 294 name: "fallback to keychain", 295 args: args, 296 assertions: []traitAssertion{ 297 assertInOutput("source=OciRegistry"), 298 assertInOutput(image), 299 assertInOutput(fmt.Sprintf("no registry credentials configured for %q, using the default keychain", host)), 300 }, 301 }, 302 { 303 name: "use creds", 304 args: args, 305 env: map[string]string{ 306 "SYFT_REGISTRY_AUTH_AUTHORITY": host, 307 "SYFT_REGISTRY_AUTH_USERNAME": "username", 308 "SYFT_REGISTRY_AUTH_PASSWORD": "password", 309 }, 310 assertions: []traitAssertion{ 311 assertInOutput("source=OciRegistry"), 312 assertInOutput(image), 313 assertInOutput(fmt.Sprintf(`using basic auth for registry "%s"`, host)), 314 }, 315 }, 316 { 317 name: "use token", 318 args: args, 319 env: map[string]string{ 320 "SYFT_REGISTRY_AUTH_AUTHORITY": host, 321 "SYFT_REGISTRY_AUTH_TOKEN": "my-token", 322 }, 323 assertions: []traitAssertion{ 324 assertInOutput("source=OciRegistry"), 325 assertInOutput(image), 326 assertInOutput(fmt.Sprintf(`using token for registry "%s"`, host)), 327 }, 328 }, 329 { 330 name: "not enough info fallback to keychain", 331 args: args, 332 env: map[string]string{ 333 "SYFT_REGISTRY_AUTH_AUTHORITY": host, 334 }, 335 assertions: []traitAssertion{ 336 assertInOutput("source=OciRegistry"), 337 assertInOutput(image), 338 assertInOutput(fmt.Sprintf(`no registry credentials configured for %q, using the default keychain`, host)), 339 }, 340 }, 341 { 342 name: "allows insecure http flag", 343 args: args, 344 env: map[string]string{ 345 "SYFT_REGISTRY_INSECURE_USE_HTTP": "true", 346 }, 347 assertions: []traitAssertion{ 348 assertInOutput("insecure-use-http: true"), 349 }, 350 }, 351 { 352 name: "use tls configuration", 353 args: args, 354 env: map[string]string{ 355 "SYFT_REGISTRY_AUTH_TLS_CERT": "place.crt", 356 "SYFT_REGISTRY_AUTH_TLS_KEY": "place.key", 357 }, 358 assertions: []traitAssertion{ 359 assertInOutput("using custom TLS credentials from"), 360 }, 361 }, 362 } 363 364 for _, test := range tests { 365 t.Run(test.name, func(t *testing.T) { 366 cmd, stdout, stderr := runSyft(t, test.env, test.args...) 367 for _, traitAssertionFn := range test.assertions { 368 traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) 369 } 370 logOutputOnFailure(t, cmd, stdout, stderr) 371 }) 372 } 373 }