From bec8bb4b4f1e64db8b6cf4f8666da78a185eac3d Mon Sep 17 00:00:00 2001 From: Eran Turgeman <81029514+eranturgeman@users.noreply.github.com> Date: Sun, 24 Mar 2024 13:51:40 +0200 Subject: [PATCH] Add Pnpm support (#656) --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/workflows/test.yml | 5 + go.mod | 30 ++-- go.sum | 55 ++++---- packagehandlers/commonpackagehandler.go | 61 ++++++++ packagehandlers/gradlepackagehandler.go | 29 +--- packagehandlers/mavenpackagehandler.go | 2 +- packagehandlers/nugetpackagehandler.go | 61 ++------ packagehandlers/packagehandlers_test.go | 180 +++++++++++++++++++----- packagehandlers/pnpmpackagehandler.go | 103 ++++++++++++++ 10 files changed, 383 insertions(+), 144 deletions(-) create mode 100644 packagehandlers/pnpmpackagehandler.go diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d250e064e..f5d587c96 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,6 @@ - [ ] All [tests](https://github.com/jfrog/frogbot#tests) passed. If this feature is not already covered by the tests, I added new tests. - [ ] This pull request is on the dev branch. - [ ] I used gofmt for formatting the code before submitting the pull request. +- [ ] Update [documentation](https://github.com/jfrog/documentation) about new features / new supported technologies --- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a764a729..b10211538 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -106,6 +106,11 @@ jobs: with: dotnet-version: "6.x" + - name: Install Pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + # Generate mocks - name: Generate mocks run: go generate ./... diff --git a/go.mod b/go.mod index 524690f94..d2631acdf 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,18 @@ require ( github.com/go-git/go-git/v5 v5.11.0 github.com/golang/mock v1.6.0 github.com/google/go-github/v45 v45.2.0 - github.com/jfrog/build-info-go v1.9.23 + github.com/jfrog/build-info-go v1.9.24 github.com/jfrog/froggit-go v1.14.6 - github.com/jfrog/gofrog v1.6.0 - github.com/jfrog/jfrog-cli-core/v2 v2.48.1 + github.com/jfrog/gofrog v1.6.3 + github.com/jfrog/jfrog-cli-core/v2 v2.49.0 github.com/jfrog/jfrog-cli-security v1.0.3 - github.com/jfrog/jfrog-client-go v1.37.1 + github.com/jfrog/jfrog-client-go v1.38.0 github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/owenrumney/go-sarif/v2 v2.3.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.1 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 gopkg.in/yaml.v3 v3.0.1 ) @@ -56,7 +56,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jedib0t/go-pretty/v6 v6.5.4 // indirect + github.com/jedib0t/go-pretty/v6 v6.5.5 // indirect github.com/jfrog/archiver/v3 v3.6.0 // indirect github.com/jfrog/jfrog-apps-config v1.0.1 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect @@ -103,27 +103,29 @@ require ( github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/mod v0.16.0 // indirect + golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240321095315-72b008905aa2 + replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go v1.14.7-0.20240324075617-8b1026034580 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev -// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security dev +replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.0.5-0.20240324085318-cac74799861e // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev diff --git a/go.sum b/go.sum index 3eba90ed3..574a03a28 100644 --- a/go.sum +++ b/go.sum @@ -886,24 +886,24 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.5.4 h1:gOGo0613MoqUcf0xCj+h/V3sHDaZasfv152G6/5l91s= -github.com/jedib0t/go-pretty/v6 v6.5.4/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= +github.com/jedib0t/go-pretty/v6 v6.5.5 h1:PpIU8lOjxvVYGGKule0QxxJfNysUSbC9lggQU2cpZJc= +github.com/jedib0t/go-pretty/v6 v6.5.5/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= -github.com/jfrog/build-info-go v1.9.23 h1:+TwUIBEJwRvz9skR8xBfY5ti8Vl4Z6iMCkFbkclnEN0= -github.com/jfrog/build-info-go v1.9.23/go.mod h1:QHcKuesY4MrBVBuEwwBz4uIsX6mwYuMEDV09ng4AvAU= +github.com/jfrog/build-info-go v1.9.24 h1:MjT+4bYecbNQ+dbLczg0lkE5DoLAhdyrF0cRXtnEJqI= +github.com/jfrog/build-info-go v1.9.24/go.mod h1:CaCKqcg3V2W9/ZysE4ZvXZMgsvunclhjrTTQQGp3CzM= github.com/jfrog/froggit-go v1.14.7-0.20240324075617-8b1026034580 h1:gszovE2btg1q9Lw8FIlD6G4DIeujOkUfKdP5nhxv1Ys= github.com/jfrog/froggit-go v1.14.7-0.20240324075617-8b1026034580/go.mod h1:TEJSzgiV+3D/GVGE8Y6j46ut1jrBLD1FL6WdMdKwwCE= -github.com/jfrog/gofrog v1.6.0 h1:jOwb37nHY2PnxePNFJ6e6279Pgkr3di05SbQQw47Mq8= -github.com/jfrog/gofrog v1.6.0/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= +github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc= +github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.48.1 h1:rRqI82btSFKFStGd7uEiheeBAuEjrw+ZZbE1abaKUBU= -github.com/jfrog/jfrog-cli-core/v2 v2.48.1/go.mod h1:9aZHtR9x7s9VUa5AalOjJkxMMPSgxXgQ5hdU3vzMwcs= -github.com/jfrog/jfrog-cli-security v1.0.3 h1:TUfPmMEavLZzxvHJSzuXg1m9OQolGL/atE8cRbuPvls= -github.com/jfrog/jfrog-cli-security v1.0.3/go.mod h1:NHmNHYlF6g4QSDyTQ3yUM57+WXZQfqskc2C1Mxj/FQY= -github.com/jfrog/jfrog-client-go v1.37.1 h1:BqIWGPajC5vhUo5dcQ9KEJr0EVANr/O4cfEqRYvzvRg= -github.com/jfrog/jfrog-client-go v1.37.1/go.mod h1:y+zeO0LeT2uHoHs4/fXHrm5dfF02bg6Dw3cNJxgJ5LY= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240321095315-72b008905aa2 h1:wJ9Tn8D+koRVNuVdX5f0+FBxuEmVuY6hgCQZsCIWV0U= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240321095315-72b008905aa2/go.mod h1:XZP7fmNBBoieQTUE2p2mvA8h/CFO5z4PE7KW1s2cdNk= +github.com/jfrog/jfrog-cli-security v1.0.5-0.20240324085318-cac74799861e h1:uuV2N+5BkuVnD6dlXnM79EcQ0gx/fiUN57zO1ieCgkU= +github.com/jfrog/jfrog-cli-security v1.0.5-0.20240324085318-cac74799861e/go.mod h1:P3r0ebMCrBiLBXdwq2j4eVqySEWd4NzDleUkvP22V5I= +github.com/jfrog/jfrog-client-go v1.38.0 h1:0QP4/dSmJe0oYUrAqzoPDpGdJHcrOeq9mycnb0pSxqQ= +github.com/jfrog/jfrog-client-go v1.38.0/go.mod h1:EHRLxpu0pIT7+ulYDNQ7IeieYBHMQeEPr8CoBHoJzQY= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -1055,8 +1055,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= @@ -1130,8 +1131,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1147,8 +1148,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1191,8 +1192,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1254,8 +1255,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1398,8 +1399,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1411,8 +1412,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1500,8 +1501,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/packagehandlers/commonpackagehandler.go b/packagehandlers/commonpackagehandler.go index b607881aa..16c7a0a3d 100644 --- a/packagehandlers/commonpackagehandler.go +++ b/packagehandlers/commonpackagehandler.go @@ -6,7 +6,10 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/utils/log" + "io/fs" "os/exec" + "path/filepath" + "regexp" "strings" ) @@ -36,6 +39,8 @@ func GetCompatiblePackageHandler(vulnDetails *utils.VulnerabilityDetails, detail handler = &NugetPackageHandler{} case coreutils.Gradle: handler = &GradlePackageHandler{} + case coreutils.Pnpm: + handler = &PnpmPackageHandler{} default: handler = &UnsupportedPackageHandler{} } @@ -84,3 +89,59 @@ func getFixedPackage(impactedPackage string, versionOperator string, suggestedFi fixedPackageArgs = strings.Split(fixedPackageString, " ") return } + +// Recursively scans the current directory for descriptor files based on the provided list of suffixes, while excluding paths that match the specified exclusion patterns. +// The patternsToExclude must be provided as regexp patterns. For instance, if the pattern ".*node_modules.*" is provided, any paths containing "node_modules" will be excluded from the result. +// Returns a slice of all discovered descriptor files, represented as absolute paths. +func (cph *CommonPackageHandler) GetAllDescriptorFilesFullPaths(descriptorFilesSuffixes []string, patternsToExclude ...string) (descriptorFilesFullPaths []string, err error) { + if len(descriptorFilesSuffixes) == 0 { + return + } + + var regexpPatternsCompilers []*regexp.Regexp + for _, patternToExclude := range patternsToExclude { + regexpPatternsCompilers = append(regexpPatternsCompilers, regexp.MustCompile(patternToExclude)) + } + + err = filepath.WalkDir(".", func(path string, d fs.DirEntry, innerErr error) error { + if innerErr != nil { + return fmt.Errorf("an error has occurred when attempting to access or traverse the file system: %w", innerErr) + } + + for _, regexpCompiler := range regexpPatternsCompilers { + if match := regexpCompiler.FindString(path); match != "" { + return filepath.SkipDir + } + } + + for _, assetFileSuffix := range descriptorFilesSuffixes { + if strings.HasSuffix(path, assetFileSuffix) { + var absFilePath string + absFilePath, innerErr = filepath.Abs(path) + if innerErr != nil { + return fmt.Errorf("couldn't retrieve file's absolute path for './%s': %w", path, innerErr) + } + descriptorFilesFullPaths = append(descriptorFilesFullPaths, absFilePath) + } + } + return nil + }) + if err != nil { + err = fmt.Errorf("failed to get descriptor files absolute paths: %w", err) + } + return +} + +// This function adjusts the name and version of a dependency to conform to a regular expression format and constructs the complete regular expression pattern for searching. +// Note: 'dependencyLineFormat' should be a template with two placeholders to be populated. The first one will be replaced with 'impactedName', and the second one with 'impactedVersion'. +// Note: All supplied arguments are converted to lowercase. Hence, when utilizing this function, the file in which we search for the patterns must also be converted to lowercase. +// Note: This function may not support all package manager dependency formats. It is designed for package managers where the dependency's name consists of a single component. +// For example, in Gradle descriptors, a dependency line may consist of two components for the dependency's name (e.g., implementation group: 'junit', name: 'junit', version: '4.7'), therefore this func cannot be utilized in this case. +func GetVulnerabilityRegexCompiler(impactedName, impactedVersion, dependencyLineFormat string) *regexp.Regexp { + // We replace '.' with '\\.' since '.' is a special character in regexp patterns, and we want to capture the character '.' itself + // To avoid dealing with case sensitivity we lower all characters in the package's name and in the file we check + regexpFitImpactedName := strings.ToLower(strings.ReplaceAll(impactedName, ".", "\\.")) + regexpFitImpactedVersion := strings.ToLower(strings.ReplaceAll(impactedVersion, ".", "\\.")) + regexpCompleteFormat := fmt.Sprintf(strings.ToLower(dependencyLineFormat), regexpFitImpactedName, regexpFitImpactedVersion) + return regexp.MustCompile(regexpCompleteFormat) +} diff --git a/packagehandlers/gradlepackagehandler.go b/packagehandlers/gradlepackagehandler.go index cb1f2d3e2..7a6c511d4 100644 --- a/packagehandlers/gradlepackagehandler.go +++ b/packagehandlers/gradlepackagehandler.go @@ -3,9 +3,7 @@ package packagehandlers import ( "fmt" "github.com/jfrog/frogbot/v2/utils" - "io/fs" "os" - "path/filepath" "regexp" "strings" ) @@ -22,6 +20,8 @@ const ( // Example: group: "junit", name: "junit", version: "1.0.0" | group = "junit", name = "junit", version = "1.0.0" var directMapWithVersionRegexp = getMapRegexpEntry("group") + "," + getMapRegexpEntry("name") + "," + getMapRegexpEntry("version") +var gradleDescriptorsSuffixes = []string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix} + func getMapRegexpEntry(mapEntry string) string { return fmt.Sprintf(directMapRegexpEntry, mapEntry) + apostrophes + "%s" + apostrophes } @@ -53,13 +53,14 @@ func (gph *GradlePackageHandler) updateDirectDependency(vulnDetails *utils.Vulne // A gradle project may contain several descriptor files in several sub-modules. Each vulnerability may be found in each of the descriptor files. // Therefore we iterate over every descriptor file for each vulnerability and try to find and fix it. - descriptorFilesPaths, err := getDescriptorFilesPaths() + var descriptorFilesFullPaths []string + descriptorFilesFullPaths, err = gph.GetAllDescriptorFilesFullPaths(gradleDescriptorsSuffixes) if err != nil { return } isAnyDescriptorFileChanged := false - for _, descriptorFilePath := range descriptorFilesPaths { + for _, descriptorFilePath := range descriptorFilesFullPaths { var isFileChanged bool isFileChanged, err = gph.fixVulnerabilityIfExists(descriptorFilePath, vulnDetails) if err != nil { @@ -85,26 +86,6 @@ func isVersionSupportedForFix(impactedVersion string) bool { return true } -// Collects all descriptor files absolute paths -func getDescriptorFilesPaths() (descriptorFilesPaths []string, err error) { - err = filepath.WalkDir(".", func(path string, d fs.DirEntry, innerErr error) error { - if innerErr != nil { - return fmt.Errorf("error has occured when trying to access or traverse the files system: %s", err.Error()) - } - - if strings.HasSuffix(path, groovyDescriptorFileSuffix) || strings.HasSuffix(path, kotlinDescriptorFileSuffix) { - var absFilePath string - absFilePath, innerErr = filepath.Abs(path) - if innerErr != nil { - return fmt.Errorf("couldn't retrieve file's absolute path for './%s':%s", path, innerErr.Error()) - } - descriptorFilesPaths = append(descriptorFilesPaths, absFilePath) - } - return nil - }) - return -} - // Fixes all direct occurrences of the given vulnerability in the given descriptor file, if vulnerability occurs func (gph *GradlePackageHandler) fixVulnerabilityIfExists(descriptorFilePath string, vulnDetails *utils.VulnerabilityDetails) (isFileChanged bool, err error) { byteFileContent, err := os.ReadFile(descriptorFilePath) diff --git a/packagehandlers/mavenpackagehandler.go b/packagehandlers/mavenpackagehandler.go index 58e0e82ae..88e72ca7b 100644 --- a/packagehandlers/mavenpackagehandler.go +++ b/packagehandlers/mavenpackagehandler.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "github.com/jfrog/frogbot/v2/utils" - "github.com/jfrog/jfrog-cli-core/v2/utils/java" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca/java" "github.com/jfrog/jfrog-client-go/utils/log" "golang.org/x/exp/slices" "os" diff --git a/packagehandlers/nugetpackagehandler.go b/packagehandlers/nugetpackagehandler.go index 88b1c05b0..7c4b0543a 100644 --- a/packagehandlers/nugetpackagehandler.go +++ b/packagehandlers/nugetpackagehandler.go @@ -4,19 +4,17 @@ import ( "errors" "fmt" "github.com/jfrog/frogbot/v2/utils" - "io/fs" "os" "path" - "path/filepath" "regexp" "strings" ) const ( - dotnetUpdateCmdPackageExtraArg = "package" - dotnetNoRestoreFlag = "--no-restore" - dotnetAssetsFilesSuffix = "csproj" - dotnetDependencyRegexpLowerCaseFormat = "include=[\\\"|\\']%s[\\\"|\\']\\s*version=[\\\"|\\']%s[\\\"|\\']" + dotnetUpdateCmdPackageExtraArg = "package" + dotnetNoRestoreFlag = "--no-restore" + dotnetAssetsFilesSuffix = "csproj" + dotnetDependencyRegexpPattern = "include=[\\\"|\\']%s[\\\"|\\']\\s*version=[\\\"|\\']%s[\\\"|\\']" ) type NugetPackageHandler struct { @@ -36,8 +34,8 @@ func (nph *NugetPackageHandler) UpdateDependency(vulnDetails *utils.Vulnerabilit } func (nph *NugetPackageHandler) updateDirectDependency(vulnDetails *utils.VulnerabilityDetails) (err error) { - var assetsFilePaths []string - assetsFilePaths, err = getAssetsFilesPaths() + var descriptorFilesFullPaths []string + descriptorFilesFullPaths, err = nph.GetAllDescriptorFilesFullPaths([]string{dotnetAssetsFilesSuffix}) if err != nil { return } @@ -48,14 +46,14 @@ func (nph *NugetPackageHandler) updateDirectDependency(vulnDetails *utils.Vulner return } - vulnRegexpCompiler := getVulnerabilityRegexCompiler(vulnDetails.ImpactedDependencyName, vulnDetails.ImpactedDependencyVersion) + vulnRegexpCompiler := GetVulnerabilityRegexCompiler(vulnDetails.ImpactedDependencyName, vulnDetails.ImpactedDependencyVersion, dotnetDependencyRegexpPattern) var isAnyFileChanged bool - for _, assetFilePath := range assetsFilePaths { + for _, descriptorFilePath := range descriptorFilesFullPaths { var isFileChanged bool - isFileChanged, err = nph.fixVulnerabilityIfExists(vulnDetails, assetFilePath, vulnRegexpCompiler, wd) + isFileChanged, err = nph.fixVulnerabilityIfExists(vulnDetails, descriptorFilePath, wd, vulnRegexpCompiler) if err != nil { - err = fmt.Errorf("failed to update asset file '%s': %s", assetFilePath, err.Error()) + err = fmt.Errorf("failed to update asset file '%s': %s", descriptorFilePath, err.Error()) return } @@ -69,37 +67,17 @@ func (nph *NugetPackageHandler) updateDirectDependency(vulnDetails *utils.Vulner return } -func getAssetsFilesPaths() (assetsFilePaths []string, err error) { - err = filepath.WalkDir(".", func(path string, d fs.DirEntry, innerErr error) error { - if innerErr != nil { - return fmt.Errorf("an error has occurred when attempting to access or traverse the file system: %s", innerErr.Error()) - } - - if strings.HasSuffix(path, dotnetAssetsFilesSuffix) { - var absFilePath string - absFilePath, innerErr = filepath.Abs(path) - if innerErr != nil { - return fmt.Errorf("couldn't retrieve file's absolute path for './%s': %s", path, innerErr.Error()) - } - assetsFilePaths = append(assetsFilePaths, absFilePath) - } - return nil - }) - return -} - -func (nph *NugetPackageHandler) fixVulnerabilityIfExists(vulnDetails *utils.VulnerabilityDetails, assetFilePath string, vulnRegexpCompiler *regexp.Regexp, originalWd string) (isFileChanged bool, err error) { - modulePath := path.Dir(assetFilePath) +func (nph *NugetPackageHandler) fixVulnerabilityIfExists(vulnDetails *utils.VulnerabilityDetails, descriptorFilePath, originalWd string, vulnRegexpCompiler *regexp.Regexp) (isFileChanged bool, err error) { + modulePath := path.Dir(descriptorFilePath) var fileData []byte - fileData, err = os.ReadFile(assetFilePath) + fileData, err = os.ReadFile(descriptorFilePath) if err != nil { - err = fmt.Errorf("failed to read file '%s': %s", assetFilePath, err.Error()) + err = fmt.Errorf("failed to read file '%s': %s", descriptorFilePath, err.Error()) return } - fileContent := strings.ToLower(string(fileData)) - if matchingRow := vulnRegexpCompiler.FindString(fileContent); matchingRow != "" { + if matchingRow := vulnRegexpCompiler.FindString(strings.ToLower(string(fileData))); matchingRow != "" { err = os.Chdir(modulePath) if err != nil { err = fmt.Errorf("failed to change directory to '%s': %s", modulePath, err.Error()) @@ -117,12 +95,3 @@ func (nph *NugetPackageHandler) fixVulnerabilityIfExists(vulnDetails *utils.Vuln } return } - -func getVulnerabilityRegexCompiler(impactedName string, impactedVersion string) *regexp.Regexp { - // We replace '.' with '\\.' since '.' is a special character in regexp patterns, and we want to capture the character '.' itself - // To avoid dealing with case sensitivity we lower all characters in the package's name and in the file we check - regexpFitImpactedName := strings.ToLower(strings.ReplaceAll(impactedName, ".", "\\.")) - regexpFitImpactedVersion := strings.ToLower(strings.ReplaceAll(impactedVersion, ".", "\\.")) - regexpCompleteFormat := fmt.Sprintf(dotnetDependencyRegexpLowerCaseFormat, regexpFitImpactedName, regexpFitImpactedVersion) - return regexp.MustCompile(regexpCompleteFormat) -} diff --git a/packagehandlers/packagehandlers_test.go b/packagehandlers/packagehandlers_test.go index 38ced39df..3af63bf6d 100644 --- a/packagehandlers/packagehandlers_test.go +++ b/packagehandlers/packagehandlers_test.go @@ -6,7 +6,7 @@ import ( biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/frogbot/v2/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/java" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca/java" "github.com/jfrog/jfrog-cli-security/formats" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/stretchr/testify/assert" @@ -290,6 +290,30 @@ func TestUpdateDependency(t *testing.T) { fixSupported: true, }, }, + + // Pnpm test cases + { + // This test case directs to non-existing directory. It only checks if the dependency update is blocked if the vulnerable dependency is not a direct dependency + { + vulnDetails: &utils.VulnerabilityDetails{ + SuggestedFixedVersion: "0.8.4", + VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pnpm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "mpath"}}, + }, + scanDetails: scanDetails, + fixSupported: false, + testDirName: "npm", + }, + { + vulnDetails: &utils.VulnerabilityDetails{ + SuggestedFixedVersion: "1.2.6", + IsDirectDependency: true, + VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pnpm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist", ImpactedDependencyVersion: "1.2.5"}}, + }, + scanDetails: scanDetails, + fixSupported: true, + testDirName: "npm", + }, + }, } for _, testBatch := range testCases { @@ -658,12 +682,26 @@ func uniquePackageManagerChecks(t *testing.T, test dependencyFixTest) { packageDescriptor := extraArgs[0] assertFixVersionInPackageDescriptor(t, test, packageDescriptor) case coreutils.Gradle: - descriptorFilesPaths, err := getDescriptorFilesPaths() + var gph GradlePackageHandler + descriptorFilesPaths, err := gph.GetAllDescriptorFilesFullPaths([]string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix}) assert.NoError(t, err) assert.Equal(t, len(descriptorFilesPaths), 2, "incorrect number of descriptor files found") for _, packageDescriptor := range descriptorFilesPaths { assertFixVersionInPackageDescriptor(t, test, packageDescriptor) } + case coreutils.Pnpm: + var pnpm PnpmPackageHandler + descriptorFilesPaths, err := pnpm.GetAllDescriptorFilesFullPaths([]string{pnpmDescriptorFileSuffix}) + assert.NoError(t, err) + assert.Equal(t, len(descriptorFilesPaths), 1, "incorrect number of descriptor files found") + for _, packageDescriptor := range descriptorFilesPaths { + assertFixVersionInPackageDescriptor(t, test, packageDescriptor) + dirPath := filepath.Dir(packageDescriptor) + var nodeModulesExist bool + nodeModulesExist, err = fileutils.IsDirExists(filepath.Join(dirPath, "node_modules"), false) + assert.NoError(t, err) + assert.False(t, nodeModulesExist) + } default: } } @@ -698,22 +736,22 @@ func TestNugetFixVulnerabilityIfExists(t *testing.T) { assert.NoError(t, os.Chdir(testRootDir)) }() - assetFiles, err := getAssetsFilesPaths() - assert.NoError(t, err) - testedAssetFile := assetFiles[0] - nph := &NugetPackageHandler{} + descriptorFiles, err := nph.GetAllDescriptorFilesFullPaths([]string{dotnetAssetsFilesSuffix}) + assert.NoError(t, err) + testedDescriptorFile := descriptorFiles[0] + for _, testcase := range testcases { - vulnRegexpCompiler := getVulnerabilityRegexCompiler(testcase.vulnerabilityDetails.ImpactedDependencyName, testcase.vulnerabilityDetails.ImpactedDependencyVersion) + vulnRegexpCompiler := GetVulnerabilityRegexCompiler(testcase.vulnerabilityDetails.ImpactedDependencyName, testcase.vulnerabilityDetails.ImpactedDependencyVersion, dotnetDependencyRegexpPattern) var isFileChanged bool - isFileChanged, err = nph.fixVulnerabilityIfExists(testcase.vulnerabilityDetails, testedAssetFile, vulnRegexpCompiler, tmpDir) + isFileChanged, err = nph.fixVulnerabilityIfExists(testcase.vulnerabilityDetails, testedDescriptorFile, tmpDir, vulnRegexpCompiler) assert.NoError(t, err) assert.True(t, isFileChanged) } var fixedFileContent []byte - fixedFileContent, err = os.ReadFile(testedAssetFile) + fixedFileContent, err = os.ReadFile(testedDescriptorFile) fixedFileContentString := string(fixedFileContent) assert.NoError(t, err) @@ -750,26 +788,6 @@ func TestGetFixedPackage(t *testing.T) { } } -func TestGradleGetDescriptorFilesPaths(t *testing.T) { - currDir, err := os.Getwd() - assert.NoError(t, err) - tmpDir, err := os.MkdirTemp("", "") - assert.NoError(t, err) - assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle"), tmpDir, true, nil)) - assert.NoError(t, os.Chdir(tmpDir)) - defer func() { - assert.NoError(t, os.Chdir(currDir)) - }() - finalPath, err := os.Getwd() - assert.NoError(t, err) - - expectedResults := []string{filepath.Join(finalPath, groovyDescriptorFileSuffix), filepath.Join(finalPath, "innerProjectForTest", kotlinDescriptorFileSuffix)} - - buildFilesPaths, err := getDescriptorFilesPaths() - assert.NoError(t, err) - assert.ElementsMatch(t, expectedResults, buildFilesPaths) -} - func TestGradleFixVulnerabilityIfExists(t *testing.T) { var testcases = []struct { vulnerabilityDetails *utils.VulnerabilityDetails @@ -801,11 +819,11 @@ func TestGradleFixVulnerabilityIfExists(t *testing.T) { assert.NoError(t, os.Chdir(currDir)) }() - descriptorFiles, err := getDescriptorFilesPaths() - assert.NoError(t, err) - gph := GradlePackageHandler{} + descriptorFiles, err := gph.GetAllDescriptorFilesFullPaths([]string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix}) + assert.NoError(t, err) + for _, descriptorFile := range descriptorFiles { for _, testcase := range testcases { var isFileChanged bool @@ -872,3 +890,101 @@ func TestGradleIsVersionSupportedForFix(t *testing.T) { assert.Equal(t, testcase.expectedResult, isVersionSupportedForFix(testcase.impactedVersion)) } } + +func TestGetAllDescriptorFilesFullPaths(t *testing.T) { + var testcases = []struct { + testProjectRepo string + suffixesToSearch []string + expectedResultSuffixes []string + patternsToExclude []string + }{ + { + testProjectRepo: "dotnet", + suffixesToSearch: []string{dotnetAssetsFilesSuffix}, + expectedResultSuffixes: []string{"dotnet.csproj"}, + }, + { + testProjectRepo: "gradle", + suffixesToSearch: []string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix}, + expectedResultSuffixes: []string{filepath.Join("innerProjectForTest", "build.gradle.kts"), "build.gradle"}, + }, + // This test case verifies that paths containing excluded patterns are omitted from the output + { + testProjectRepo: "gradle", + suffixesToSearch: []string{groovyDescriptorFileSuffix, kotlinDescriptorFileSuffix}, + expectedResultSuffixes: []string{"build.gradle"}, + patternsToExclude: []string{".*innerProjectForTest.*"}, + }, + } + + currDir, outerErr := os.Getwd() + assert.NoError(t, outerErr) + + for _, testcase := range testcases { + tmpDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", testcase.testProjectRepo), tmpDir, true, nil)) + assert.NoError(t, os.Chdir(tmpDir)) + + finalDirPath, err := os.Getwd() + assert.NoError(t, err) + + var expectedResults []string + for _, suffix := range testcase.expectedResultSuffixes { + expectedResults = append(expectedResults, filepath.Join(finalDirPath, suffix)) + } + + var cph CommonPackageHandler + descriptorFilesFullPaths, err := cph.GetAllDescriptorFilesFullPaths(testcase.suffixesToSearch, testcase.patternsToExclude...) + assert.NoError(t, err) + assert.ElementsMatch(t, expectedResults, descriptorFilesFullPaths) + + assert.NoError(t, os.Chdir(currDir)) + assert.NoError(t, fileutils.RemoveTempDir(tmpDir)) + } +} + +func TestPnpmFixVulnerabilityIfExists(t *testing.T) { + testRootDir, err := os.Getwd() + assert.NoError(t, err) + + tmpDir, err := os.MkdirTemp("", "") + defer func() { + assert.NoError(t, fileutils.RemoveTempDir(tmpDir)) + }() + assert.NoError(t, err) + assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "npm"), tmpDir, true, nil)) + assert.NoError(t, os.Chdir(tmpDir)) + defer func() { + assert.NoError(t, os.Chdir(testRootDir)) + }() + + vulnerabilityDetails := &utils.VulnerabilityDetails{ + SuggestedFixedVersion: "1.2.6", + IsDirectDependency: true, + VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pnpm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist", ImpactedDependencyVersion: "1.2.5"}}, + } + pnpm := &PnpmPackageHandler{} + + descriptorFiles, err := pnpm.GetAllDescriptorFilesFullPaths([]string{pnpmDescriptorFileSuffix}) + assert.NoError(t, err) + descriptorFileToTest := descriptorFiles[0] + + vulnRegexpCompiler := GetVulnerabilityRegexCompiler(vulnerabilityDetails.ImpactedDependencyName, vulnerabilityDetails.ImpactedDependencyVersion, pnpmDependencyRegexpPattern) + var isFileChanged bool + isFileChanged, err = pnpm.fixVulnerabilityIfExists(vulnerabilityDetails, descriptorFileToTest, tmpDir, vulnRegexpCompiler) + assert.NoError(t, err) + assert.True(t, isFileChanged) + + var fixedFileContent []byte + fixedFileContent, err = os.ReadFile(descriptorFileToTest) + fixedFileContentString := string(fixedFileContent) + + assert.NoError(t, err) + assert.NotContains(t, fixedFileContentString, "\"minimist\": \"1.2.5\"") + assert.Contains(t, fixedFileContentString, "\"minimist\": \"1.2.6\"") + + nodeModulesExist, err := fileutils.IsDirExists(filepath.Join(tmpDir, "node_modules"), false) + assert.NoError(t, err) + assert.False(t, nodeModulesExist) +} diff --git a/packagehandlers/pnpmpackagehandler.go b/packagehandlers/pnpmpackagehandler.go new file mode 100644 index 000000000..90a1e806e --- /dev/null +++ b/packagehandlers/pnpmpackagehandler.go @@ -0,0 +1,103 @@ +package packagehandlers + +import ( + "errors" + "fmt" + "github.com/jfrog/frogbot/v2/utils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +const ( + pnpmDependencyRegexpPattern = "\\s*\"%s\"\\s*:\\s*\"[~|^]?%s\"" + pnpmDescriptorFileSuffix = "package.json" + nodeModulesPathPattern = ".*node_modules.*" +) + +type PnpmPackageHandler struct { + CommonPackageHandler +} + +func (pnpm *PnpmPackageHandler) UpdateDependency(vulnDetails *utils.VulnerabilityDetails) error { + if vulnDetails.IsDirectDependency { + return pnpm.updateDirectDependency(vulnDetails) + } + + return &utils.ErrUnsupportedFix{ + PackageName: vulnDetails.ImpactedDependencyName, + FixedVersion: vulnDetails.SuggestedFixedVersion, + ErrorType: utils.IndirectDependencyFixNotSupported, + } +} + +func (pnpm *PnpmPackageHandler) updateDirectDependency(vulnDetails *utils.VulnerabilityDetails) (err error) { + descriptorFilesFullPaths, err := pnpm.CommonPackageHandler.GetAllDescriptorFilesFullPaths([]string{pnpmDescriptorFileSuffix}, nodeModulesPathPattern) + if err != nil { + return err + } + + wd, err := os.Getwd() + if err != nil { + err = fmt.Errorf("failed to get current working directory: %s", err.Error()) + return err + } + + vulnRegexpCompiler := GetVulnerabilityRegexCompiler(vulnDetails.ImpactedDependencyName, vulnDetails.ImpactedDependencyVersion, pnpmDependencyRegexpPattern) + + var anyDescriptorChanged bool + for _, descriptorFile := range descriptorFilesFullPaths { + var isFileChanged bool + isFileChanged, err = pnpm.fixVulnerabilityIfExists(vulnDetails, descriptorFile, wd, vulnRegexpCompiler) + if err != nil { + return err + } + anyDescriptorChanged = anyDescriptorChanged || isFileChanged + } + if !anyDescriptorChanged { + err = fmt.Errorf("impacted package %q was not found in any descriptor files", vulnDetails.ImpactedDependencyName) + } + return err +} + +func (pnpm *PnpmPackageHandler) fixVulnerabilityIfExists(vulnDetails *utils.VulnerabilityDetails, descriptorFilePath, originalWd string, vulnRegexpCompiler *regexp.Regexp) (isFileChanged bool, err error) { + var descriptorFileData []byte + descriptorFileData, err = os.ReadFile(descriptorFilePath) + if err != nil { + err = fmt.Errorf("failed to read file '%s': %s", descriptorFilePath, err.Error()) + return isFileChanged, err + } + + // Only if the vulnerable dependency is detected in the current descriptor, we initiate a fix + if match := vulnRegexpCompiler.FindString(strings.ToLower(string(descriptorFileData))); match != "" { + modulePath := path.Dir(descriptorFilePath) + if err = os.Chdir(modulePath); err != nil { + err = fmt.Errorf("failed to change directory to '%s': %s", modulePath, err.Error()) + return isFileChanged, err + } + defer func() { + err = errors.Join(err, os.Chdir(originalWd)) + }() + + var nodeModulesDirExist bool + if nodeModulesDirExist, err = fileutils.IsDirExists(filepath.Join(modulePath, "node_modules"), false); err != nil { + return isFileChanged, err + } + + if !nodeModulesDirExist { + defer func() { + // If node_modules directory doesn't exist prior to the dependency update we aim remove it after the update. + err = errors.Join(err, fileutils.RemoveTempDir(filepath.Join(modulePath, "node_modules"))) + }() + } + + if err = pnpm.CommonPackageHandler.UpdateDependency(vulnDetails, vulnDetails.Technology.GetPackageInstallationCommand()); err != nil { + return isFileChanged, fmt.Errorf("failed to update dependency '%s' from version '%s' to '%s': %s", vulnDetails.ImpactedDependencyName, vulnDetails.ImpactedDependencyVersion, vulnDetails.SuggestedFixedVersion, err.Error()) + } + isFileChanged = true + } + return isFileChanged, err +}