Skip to content

Commit

Permalink
Recognize "403 forbidden" error from mvn command output, and clasiffy…
Browse files Browse the repository at this point in the history
… it as "forbidden" error type (#267)
  • Loading branch information
asafambar authored Aug 20, 2024
1 parent 8ae8d01 commit bb14890
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 6 deletions.
33 changes: 30 additions & 3 deletions build/maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/jfrog/build-info-go/utils"
"golang.org/x/term"
)

const (
Expand Down Expand Up @@ -340,15 +341,41 @@ func (config *mvnRunConfig) SetOutputWriter(outputWriter io.Writer) *mvnRunConfi
return config
}

func (config *mvnRunConfig) runCmd() error {
func (config *mvnRunConfig) runCmd() (err error) {
command := config.GetCmd()
command.Stderr = os.Stderr
errBuffer := bytes.NewBuffer([]byte{})
multiWriter := io.MultiWriter(os.Stderr, errBuffer)
command.Stderr = multiWriter
if config.outputWriter == nil {
command.Stdout = os.Stderr
} else {
command.Stdout = config.outputWriter
}
command.Dir = config.workspace
addColorToCmdOutput(command)
config.logger.Info("Running mvn command:", strings.Join(command.Args, " "))
return command.Run()

err = command.Run()
if err != nil {
if utils.IsForbiddenOutput(utils.Maven, errBuffer.String()) {
err = errors.Join(utils.NewForbiddenError(), err)
}
}
return
}

// To always have color in Maven's output, add "-Dstyle.color=always" to the command line arguments
func addColorToCmdOutput(command *exec.Cmd) {
if term.IsTerminal(int(os.Stderr.Fd())) {
shouldAddColor := true
for _, arg := range command.Args {
if strings.Contains(arg, "-Dstyle.color") {
shouldAddColor = false
break
}
}
if shouldAddColor {
command.Args = append(command.Args, "-Dstyle.color=always")
}
}
}
53 changes: 53 additions & 0 deletions build/maven_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"github.com/jfrog/build-info-go/tests"
"github.com/jfrog/build-info-go/utils"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -127,3 +129,54 @@ func TestGetExecutableName(t *testing.T) {
assert.Equal(t, result, mvnHome)
}
}

func TestAddColorToCmdOutput(t *testing.T) {
testCases := []struct {
name string
initialArgs []string
expectedResult string
colorArgExist bool
}{
{
name: "Not a terminal, shouldn't add color",
initialArgs: []string{"mvn"},
colorArgExist: false,
},
{
name: "Terminal supports color and existing color argument",
initialArgs: []string{"mvn", "-Dstyle.color=always"},
expectedResult: "Dstyle.color=always",
colorArgExist: true,
},
{
name: "Terminal supports color and existing color argument",
initialArgs: []string{"mvn", "-Dstyle.color=never"},
expectedResult: "Dstyle.color=never",
colorArgExist: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Mock terminal support

// Create a mock exec.Cmd object
cmd := exec.Command(tc.initialArgs[0], tc.initialArgs[1:]...)

// Call the function to test
addColorToCmdOutput(cmd)

// Check if the argument was added
containsColorArg := false
for _, arg := range cmd.Args {
if strings.Contains(arg, "Dstyle.color") {
if strings.Contains(arg, tc.expectedResult) {
containsColorArg = true
break
}
}
}
assert.Equal(t, tc.colorArgExist, containsColorArg)
})
}
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/urfave/cli/v2 v2.27.2
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
golang.org/x/term v0.23.0
)

require (
Expand All @@ -26,6 +27,6 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.23.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbR
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
48 changes: 48 additions & 0 deletions utils/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package utils

import (
"strings"
)

type PackageManager string

const (
Npm PackageManager = "npm"
Maven PackageManager = "maven"
Pip PackageManager = "pip"
Go PackageManager = "go"
)

// ForbiddenError represents a 403 Forbidden error.
type ForbiddenError struct {
Message string
}

// Error implements the error interface for ForbiddenError.
func (e *ForbiddenError) Error() string {
return "403 Forbidden"
}

// NewForbiddenError creates a new ForbiddenError with the given message.
func NewForbiddenError() *ForbiddenError {
return &ForbiddenError{}
}

// IsForbiddenOutput checks whether the provided output includes a 403 Forbidden. The various package managers have their own forbidden output formats.
func IsForbiddenOutput(tech PackageManager, cmdOutput string) bool {
switch tech {
case "npm":
return strings.Contains(strings.ToLower(cmdOutput), "403 forbidden")
case "maven":
return strings.Contains(cmdOutput, "status code: 403") ||
strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") ||
// In some cases mvn returns 500 status code even though it got 403 from artifactory.
strings.Contains(cmdOutput, "status code: 500")
case "pip":
return strings.Contains(strings.ToLower(cmdOutput), "http error 403")
case "go":
return strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") ||
strings.Contains(strings.ToLower(cmdOutput), " 403")
}
return false
}

0 comments on commit bb14890

Please sign in to comment.