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

Recognize "403 forbidden" error from mvn command output, and clasiffy it as "forbidden" error type #267

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
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("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
39 changes: 39 additions & 0 deletions utils/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package utils

import (
"strings"
)

// 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 verify the output is forbidden, each tech have its own forbidden output.
asafambar marked this conversation as resolved.
Show resolved Hide resolved
func IsForbiddenOutput(tech string, cmdOutput string) bool {
asafambar marked this conversation as resolved.
Show resolved Hide resolved
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
}
Loading