Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby reachables test - WIP #1574

Merged
merged 3 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions .github/workflows/java-reachables-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,48 +70,51 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: npm install, build
- name: setup paths
run: |
corepack enable
corepack pnpm install
corepack enable pnpm
echo "${RUNNER_TEMP}/bin" >> "$GITHUB_PATH"
echo "${GITHUB_WORKSPACE}/node_modules/.bin" >> "$GITHUB_PATH"
mkdir -p repotests
mkdir -p rubyresults
- name: npm install, build
run: |
pnpm install --package-import-method copy
pnpm config set global-bin-dir "${RUNNER_TEMP}/bin"
pnpm install -g .
- uses: actions/checkout@v4
with:
repository: 'campsite/campsite'
path: 'repotests/campsite'
ref: '10197238bbbefd9c9ac7c77467b647fd93993ba0'
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
id: ruby34
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3.4'
bundler-cache: true
id: ruby33
- name: bundle install
run: |
cd repotests/campsite/api
bundle install
rm -rf vendor/cache vendor/yarn
env:
BUNDLE_PATH: vendor/bundle
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
id: ruby34
BUNDLE_PATH: ${{ runner.temp }}/vendor/bundle
- name: generate sbom with reachables
run: |
cd repotests/campsite/api
echo "PATH: ${PATH}"
rbastgen --help
echo "ATOM_RUBY_HOME: ${ATOM_RUBY_HOME}"
echo "BUNDLE_PATH: ${BUNDLE_PATH}"
node $GITHUB_WORKSPACE/bin/cdxgen.js -p -t ruby --profile research -o bom.json . --lifecycle pre-build
echo "CDXGEN_GEM_HOME: ${CDXGEN_GEM_HOME}"
atom --help
rbastgen --help
node $GITHUB_WORKSPACE/bin/cdxgen.js -t ruby --profile research -o bom.json .
ls -lh
cp bom.json *.slices.json $GITHUB_WORKSPACE/rubyresults
env:
CDXGEN_DEBUG_MODE: debug
ATOM_RUBY_HOME: ${{ steps.ruby34.outputs.ruby-prefix }}
CDXGEN_GEM_HOME: ${{ github.workspace }}/repotests/campsite/api/vendor/bundle
CDXGEN_GEM_HOME: ${{ runner.temp }}/vendor/bundle/ruby/3.3.0
working-directory: ./repotests/campsite/api
- uses: actions/upload-artifact@v4
with:
name: rubyresults
Expand Down
33 changes: 23 additions & 10 deletions lib/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5160,33 +5160,48 @@ export function createPHPBom(path, options) {
* @param {Object} options Parse options from the cli
*/
export async function createRubyBom(path, options) {
const excludeList = (options.exclude || []).concat(["**/vendor/cache/**"]);
const gemLockExcludeList = (options.exclude || []).concat([
"**/vendor/bundle/ruby/**/Gemfile.lock",
]);
if (!hasAnyProjectType(["oci"], options, false)) {
excludeList.push("**/vendor/bundle/**");
gemLockExcludeList.push("**/vendor/cache/**");
}
const gemFiles = getAllFiles(
path,
`${options.multiProject ? "**/" : ""}Gemfile`,
options,
{
...options,
exclude: excludeList,
},
);
let gemLockFiles = getAllFiles(
path,
`${options.multiProject ? "**/" : ""}Gemfile*.lock`,
{
...options,
exclude: (options.exclude || []).concat([
"**/vendor/bundle/ruby/**/Gemfile.lock",
]),
exclude: gemLockExcludeList,
},
);
let gemspecFiles = getAllFiles(
path,
`${options.multiProject ? "**/" : ""}*.gemspec`,
options,
{
...options,
exclude: excludeList,
},
);
const gemHome = process.env.CDXGEN_GEM_HOME || process.env.GEM_HOME;
let gemHome = process.env.CDXGEN_GEM_HOME || process.env.GEM_HOME;
if (!gemHome && (process.env.BUNDLE_PATH || process.env.GEM_PATH)) {
gemHome = process.env.BUNDLE_PATH || process.env.GEM_PATH;
}
let isGemHomeEmpty = true;
// In deep mode, let's collect all gems that got installed in our custom GEM_HOME directory.
// This would improve the accuracy of any security analysis downstream at cost of a slight increase in time.
if (options.deep && process.env.CDXGEN_GEM_HOME) {
const gemHomeSpecFiles = getAllFiles(
process.env.CDXGEN_GEM_HOME,
process.env.CDXGEN_GEM_HOME || process.env.BUNDLE_PATH,
"**/specifications/**/*.gemspec",
options,
);
Expand Down Expand Up @@ -5223,9 +5238,7 @@ export async function createRubyBom(path, options) {
`${options.multiProject ? "**/" : ""}Gemfile*.lock`,
{
...options,
exclude: (options.exclude || []).concat([
"**/vendor/bundle/ruby/**/Gemfile.lock",
]),
exclude: gemLockExcludeList,
},
);
if (gemLockFiles.length) {
Expand Down
2 changes: 2 additions & 0 deletions lib/evinser/evinser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,8 @@ export function extractEndpoints(language, code) {
code = code.replaceAll("...", "");
if (code.includes("namespace ")) {
urlPrefix = code.split("namespace ").pop().split(" ")[0];
} else if (code.includes("collection do get ")) {
urlPrefix = code.split("collection do get ").pop().split(" ")[0];
}
for (const m of ["get", "post", "delete", "options", "put", "head"]) {
if (code.includes(`${m} `)) {
Expand Down
50 changes: 43 additions & 7 deletions lib/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6248,6 +6248,7 @@ export function collectGemModuleNames(
if (!gemName || gemName.startsWith("/") || gemName === ".") {
return [];
}
gemName = gemName.replace(/["']/g, "");
// Module names for some gems cannot be obtained with our one-liner
// So we keep a hard-coded list of such problematic ones.
if (RUBY_KNOWN_MODULES[gemName]) {
Expand Down Expand Up @@ -6289,6 +6290,7 @@ export function collectGemModuleNames(
// Let's retry for simple mismatches
if (gemName?.includes("-")) {
return collectGemModuleNames(
rubyCommand,
bundleCommand,
gemHome,
gemName.replaceAll("-", "/"),
Expand All @@ -6312,6 +6314,7 @@ export function collectGemModuleNames(
console.log("Retrying", gemName, "with", altGemName);
}
return collectGemModuleNames(
rubyCommand,
bundleCommand,
gemHome,
altGemName,
Expand All @@ -6320,18 +6323,27 @@ export function collectGemModuleNames(
}
if (DEBUG_MODE) {
console.log(
`Is ${altGemName} an alternative gem name for the package ${gemName}? Please let us know.`,
`Is ${altGemName} an alternative gem name for '${gemName}' package? Please let us know if this is correct.`,
);
}
}
// Gem wasn't installed or the GEM_HOME was not set correctly.
if (result?.stderr?.includes("Bundler::GemNotFound")) {
if (
result?.stderr?.includes("Bundler::GemNotFound") ||
result?.stderr?.includes("(LoadError)")
) {
return [];
}
if (DEBUG_MODE) {
if (
!result?.stderr?.includes("(NameError)") &&
!result?.stderr?.includes("(NoMethodError)") &&
!result?.stderr?.includes("(ArgumentError)") &&
DEBUG_MODE
) {
console.log(
`Unable to collect the module names exported by the gem ${gemName}.`,
);
console.log(result.stderr);
}
// Let's guess the module name based on common naming convention.
return toGemModuleNames(gemName);
Expand Down Expand Up @@ -6374,23 +6386,41 @@ export async function parseGemspecData(gemspecData, gemspecFile) {
}
let versionHackMatch = false;
gemspecData.split("\n").forEach((l) => {
versionHackMatch = false;
l = l.replace("\r", "");
l = l.replace(/\s+/g, " ").replaceAll("%q{", "").trim().replace(/}$/, "");
if (l.startsWith("#")) {
return;
}
for (const aprop of ["name", "version", "description", "homepage"]) {
if (l.includes(`.${aprop} = `)) {
pkg[aprop] = l
let value = l
.split(`.${aprop} = `)[1]
.replace(".freeze", "")
.replaceAll("''', ", "")
.replace(/"/g, "");
if (["name", "version"].includes(aprop)) {
value = value.replace(/["']/g, "");
}
pkg[aprop] = value;
return;
}
}
// Cleanup bad version strings
if (pkg?.version?.includes("::")) {
// Handle common problems
if (pkg.name === "name") {
console.log(
"Unable to identify the package name by parsing the file",
gemspecFile,
);
return;
}
if (
pkg?.version === "version" ||
pkg?.version?.includes("$") ||
pkg?.version?.includes("gem_version") ||
pkg?.version?.includes("File.") ||
pkg?.version?.includes("::")
) {
pkg.version = undefined;
// Can we find the version from the directory name?
if (gemspecFile) {
Expand All @@ -6400,6 +6430,12 @@ export async function parseGemspecData(gemspecData, gemspecFile) {
versionHackMatch = true;
}
}
if (!versionHackMatch && !pkg.version) {
console.log(
"Unable to identify the package version by parsing the file",
gemspecFile,
);
}
}
for (const aprop of ["authors", "licenses"]) {
if (l.includes(`.${aprop} = `)) {
Expand Down Expand Up @@ -6618,7 +6654,7 @@ export async function parseGemfileLockData(gemLockData, lockFile) {
if (specsFound) {
const tmpA = l.split(" (");
const nameWithPrefix = tmpA[0];
const name = tmpA[0].trim();
const name = tmpA[0].replace(/["']/g, "").trim();
const level = nameWithPrefix.replace(name, "").split(" ").length % 2;
if (
!name.length ||
Expand Down
45 changes: 40 additions & 5 deletions lib/stages/pregen/pregen.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import process from "node:process";
import {
SDKMAN_JAVA_TOOL_ALIASES,
bundleInstallWithDocker,
collectRubyInfo,
getOrInstallNvmTool,
installRubyBundler,
installRubyVersion,
Expand Down Expand Up @@ -357,15 +358,27 @@ export function prepareRubyEnv(filePath, options) {
const gemFiles = getAllFiles(
filePath,
`${options.multiProject ? "**/" : ""}Gemfile`,
options,
{
...options,
exclude: (options.exclude || []).concat([
"**/vendor/cache/**",
"**/vendor/bundle/**",
]),
},
);
if (!gemFiles.length) {
return;
}
const gemLockFiles = getAllFiles(
filePath,
`${options.multiProject ? "**/" : ""}Gemfile*.lock`,
options,
{
...options,
exclude: (options.exclude || []).concat([
"**/vendor/cache/**",
"**/vendor/bundle/**",
]),
},
);
if (gemLockFiles.length && !options.deep) {
return;
Expand Down Expand Up @@ -429,7 +442,21 @@ export function prepareRubyEnv(filePath, options) {
}
}
}
if (!rbenvPresent) {
// Do we already have this version
const existingRuby = collectRubyInfo(filePath);
if (
rubyVersionNeeded &&
existingRuby?.version?.startsWith(`ruby ${rubyVersionNeeded} `)
) {
if (DEBUG_MODE) {
console.log(`Required Ruby version ${rubyVersionNeeded} is present.`);
}
process.env.CDXGEN_RUBY_CMD = "ruby";
process.env.CDXGEN_GEM_CMD = "gem";
process.env.CDXGEN_BUNDLE_CMD = "bundle";
return;
}
if (rubyVersionNeeded && !rbenvPresent) {
console.log(
`This project requires Ruby ${rubyVersionNeeded}. cdxgen can automatically install the required version of Ruby with rbenv command.`,
);
Expand All @@ -443,7 +470,8 @@ export function prepareRubyEnv(filePath, options) {
);
}
}
} else if (rubyVersionNeeded) {
}
if (rubyVersionNeeded) {
// Should we use docker
if (isFeatureEnabled(options, "ruby-docker-install") || isWin) {
for (const agemf of gemFiles) {
Expand Down Expand Up @@ -488,6 +516,7 @@ export function prepareRubyEnv(filePath, options) {
}
process.env.CDXGEN_RUBY_CMD = join(fullToolBinDir, "ruby");
process.env.CDXGEN_GEM_CMD = join(fullToolBinDir, "gem");
process.env.CDXGEN_BUNDLE_CMD = join(fullToolBinDir, "bundle");
bundleTool = join(fullToolBinDir, "bundle");
process.env.CDXGEN_BUNDLE_CMD = bundleTool;
if (!existsSync(bundleTool)) {
Expand All @@ -507,7 +536,13 @@ export function prepareRubyEnv(filePath, options) {
const gemspecFiles = getAllFiles(
cdxgenGemHome,
"**/specifications/**/*.gemspec",
options,
{
...options,
exclude: (options.exclude || []).concat([
"**/vendor/cache/**",
"**/vendor/bundle/**",
]),
},
);
if (gemspecFiles.length > 3) {
if (DEBUG_MODE) {
Expand Down
2 changes: 1 addition & 1 deletion types/lib/cli/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading