2 Commits

Author SHA1 Message Date
Ompragash Viswanathan f84f55f3b2 Fix 2025-10-07 19:28:51 +05:30
Ompragash Viswanathan 96190838fd fix 2025-10-07 18:42:49 +05:30
3 changed files with 512 additions and 6 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ func main() {
type formatter struct{}
func (*formatter) Format(entry *logrus.Entry) ([]byte, error) {
return []byte(entry.Message), nil
return []byte(entry.Message + "\n"), nil
}
// text formatter that writes logs with level information
+91 -5
View File
@@ -13,6 +13,7 @@ import (
"mime/multipart"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
)
@@ -153,7 +154,17 @@ func (n *NexusPlugin) Run() error {
n.HttpClient = &http.Client{}
}
for _, artifact := range n.Artifacts {
// Log upload configuration summary
LogPrintln(n, "")
LogPrintln(n, "Upload Configuration:")
LogPrintln(n, fmt.Sprintf(" Nexus Version: %s", n.Version))
LogPrintln(n, fmt.Sprintf(" Server URL: %s", n.ServerUrl))
LogPrintln(n, fmt.Sprintf(" Repository: %s", n.Repository))
LogPrintln(n, fmt.Sprintf(" Format: %s", n.Format))
LogPrintln(n, fmt.Sprintf(" Total artifacts: %d", len(n.Artifacts)))
LogPrintln(n, "")
for idx, artifact := range n.Artifacts {
filePath := artifact.File
file, err := os.Open(filePath)
if err != nil {
@@ -161,6 +172,28 @@ func (n *NexusPlugin) Run() error {
continue
}
// Log individual artifact details before upload
LogPrintln(n, fmt.Sprintf("Uploading artifact %d/%d:", idx+1, len(n.Artifacts)))
// Get file size from the opened file handle
fileInfo, statErr := file.Stat()
var sizeStr string
if statErr == nil {
fileSize := float64(fileInfo.Size()) / (1024 * 1024) // Convert to MB
sizeStr = fmt.Sprintf(" (%.2f MB)", fileSize)
}
LogPrintln(n, fmt.Sprintf(" File: %s%s", filePath, sizeStr))
LogPrintln(n, fmt.Sprintf(" ArtifactId: %s", artifact.ArtifactId))
if artifact.GroupId != "" {
LogPrintln(n, fmt.Sprintf(" GroupId: %s", artifact.GroupId))
}
LogPrintln(n, fmt.Sprintf(" Version: %s", artifact.Version))
LogPrintln(n, fmt.Sprintf(" Type: %s", artifact.Type))
if artifact.Classifier != "" {
LogPrintln(n, fmt.Sprintf(" Classifier: %s", artifact.Classifier))
}
if n.Version == "nexus2" {
artifactURL := n.prepareNexus2ArtifactURL(artifact)
if err := n.uploadFileNexus2(artifactURL, file, filePath); err != nil {
@@ -186,9 +219,23 @@ func (n *NexusPlugin) Run() error {
LogPrintln(n, "Error closing file: ", err.Error())
}
fmt.Println("Successfully uploaded artifact:", filePath)
// Log enhanced success message with artifact coordinates
basename := filepath.Base(filePath)
coordinates := fmt.Sprintf("%s:%s:%s", artifact.GroupId, artifact.ArtifactId, artifact.Version)
if artifact.GroupId == "" {
coordinates = fmt.Sprintf("%s:%s", artifact.ArtifactId, artifact.Version)
}
LogPrintln(n, fmt.Sprintf("[OK] Successfully uploaded: %s -> %s", basename, coordinates))
LogPrintln(n, "")
}
// Log upload summary
totalArtifacts := len(n.Artifacts)
successCount := totalArtifacts - len(n.Failed)
LogPrintln(n, "Upload Summary:")
LogPrintln(n, fmt.Sprintf(" Total: %d, Successful: %d, Failed: %d", totalArtifacts, successCount, len(n.Failed)))
if len(n.Failed) > 0 {
return GetNewError("NexusPlugin Error in Run: some artifacts failed to upload")
}
@@ -302,7 +349,9 @@ func (n *NexusPlugin) IsMultiFileUploadArgsOk(args Args) error {
n.UserName = args.Username
n.Password = args.Password
n.Repository = args.Repository
n.ServerUrl = args.Protocol + "://" + args.ServerUrl
// Fix Bug #3: Remove trailing slashes from server URL before concatenating
serverUrl := strings.TrimRight(args.ServerUrl, "/")
n.ServerUrl = args.Protocol + "://" + serverUrl
n.GroupId = args.GroupId
n.Version = args.NexusVersion
n.Format = args.Format
@@ -382,7 +431,8 @@ func (n *NexusPlugin) IsSingleFileUploadArgsOk(args Args) error {
n.UserName = args.Username
n.Password = args.Password
n.Repository = args.Repository
n.ServerUrl = args.ServerUrl
// Fix Bug #3: Remove trailing slashes from server URL
n.ServerUrl = strings.TrimRight(args.ServerUrl, "/")
n.Format = args.Format
n.GroupId = values["CgroupId"]
n.Version = "nexus3"
@@ -456,10 +506,27 @@ func (n *NexusPlugin) uploadFileNexus2(url string, content io.Reader, filePath s
}
defer resp.Body.Close()
// Fix Bug #1: Read response body to provide detailed error messages
bodyBytes, readErr := io.ReadAll(resp.Body)
var bodyContent string
if readErr == nil {
bodyContent = string(bodyBytes)
}
if resp.StatusCode >= 400 {
fmt.Println("File upload failed status ", resp.StatusCode)
if bodyContent != "" {
fmt.Println("Response body: ", bodyContent)
return fmt.Errorf("Upload failed with status %d: %s", resp.StatusCode, bodyContent)
}
return fmt.Errorf("Upload failed with status %d", resp.StatusCode)
}
// Log success response body for debugging
if bodyContent != "" {
fmt.Println("Upload successful. Response: ", bodyContent)
}
return nil
}
@@ -487,7 +554,10 @@ func (n *NexusPlugin) uploadFileNexus3(artifact Artifact, filePath string) error
assetFieldName = fmt.Sprintf("%s.asset", n.Format)
}
fileWriter, err := writer.CreateFormFile(assetFieldName, artifact.File)
// Fix Bug #2: Extract basename from file path to avoid sending full paths to Nexus
// This handles both Linux (/path/to/file.jar) and Windows (C:\path\to\file.jar) paths
basename := filepath.Base(artifact.File)
fileWriter, err := writer.CreateFormFile(assetFieldName, basename)
if err != nil {
LogPrintln(n, "Error CreateFormFile: ", err.Error())
return err
@@ -528,11 +598,27 @@ func (n *NexusPlugin) uploadFileNexus3(artifact Artifact, filePath string) error
}
defer resp.Body.Close()
// Fix Bug #1: Read response body to provide detailed error messages
bodyBytes, readErr := io.ReadAll(resp.Body)
var bodyContent string
if readErr == nil {
bodyContent = string(bodyBytes)
}
if resp.StatusCode >= 400 {
LogPrintln(n, "Error upload failed with status: ", resp.StatusCode)
if bodyContent != "" {
LogPrintln(n, "Response body: ", bodyContent)
return fmt.Errorf("Upload failed with status %d: %s", resp.StatusCode, bodyContent)
}
return fmt.Errorf("Upload failed with status %d", resp.StatusCode)
}
// Log success response body for debugging
if bodyContent != "" {
LogPrintln(n, "Upload successful. Response: ", bodyContent)
}
return nil
}
+420
View File
@@ -185,3 +185,423 @@ func TestNexusPlugin_Run_MultiFileUpload_Success(t *testing.T) {
assert.Empty(t, plugin.Failed)
mockClient.AssertExpectations(t)
}
// Test Bug #3: URL Trailing Slash - Single File Upload
func TestIsSingleFileUploadArgsOk_TrailingSlash(t *testing.T) {
args := Args{
EnvPluginInputArgs: EnvPluginInputArgs{
Username: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com/",
Filename: "testfile.jar",
Format: "maven2",
Repository: "repo",
Attributes: "-CgroupId=com.test -CartifactId=app -Cversion=1.0.0 -Aextension=jar -Aclassifier=bin",
},
}
plugin := NexusPlugin{}
err := plugin.IsSingleFileUploadArgsOk(args)
assert.Nil(t, err)
assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Trailing slash should be removed")
}
// Test Bug #3: URL Multiple Trailing Slashes - Single File Upload
func TestIsSingleFileUploadArgsOk_MultipleTrailingSlashes(t *testing.T) {
args := Args{
EnvPluginInputArgs: EnvPluginInputArgs{
Username: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com///",
Filename: "testfile.jar",
Format: "maven2",
Repository: "repo",
Attributes: "-CgroupId=com.test -CartifactId=app -Cversion=1.0.0 -Aextension=jar -Aclassifier=bin",
},
}
plugin := NexusPlugin{}
err := plugin.IsSingleFileUploadArgsOk(args)
assert.Nil(t, err)
assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Multiple trailing slashes should be removed")
}
// Test Bug #3: URL No Trailing Slash - Single File Upload (should remain unchanged)
func TestIsSingleFileUploadArgsOk_NoTrailingSlash(t *testing.T) {
args := Args{
EnvPluginInputArgs: EnvPluginInputArgs{
Username: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Filename: "testfile.jar",
Format: "maven2",
Repository: "repo",
Attributes: "-CgroupId=com.test -CartifactId=app -Cversion=1.0.0 -Aextension=jar -Aclassifier=bin",
},
}
plugin := NexusPlugin{}
err := plugin.IsSingleFileUploadArgsOk(args)
assert.Nil(t, err)
assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "URL without trailing slash should remain unchanged")
}
// Test Bug #3: URL Trailing Slash - Multi File Upload
func TestIsMultiFileUploadArgsOk_TrailingSlash(t *testing.T) {
args := Args{
EnvPluginInputArgs: EnvPluginInputArgs{
Username: "testUser",
Password: "testPass",
Protocol: "https",
ServerUrl: "nexus.example.com/",
NexusVersion: "nexus3",
Repository: "repo",
GroupId: "com.test",
Format: "maven2",
Artifact: "[{\"file\":\"test.jar\",\"artifactId\":\"app\",\"type\":\"jar\",\"version\":\"1.0\",\"groupId\":\"com.test\"}]",
},
}
plugin := NexusPlugin{}
err := plugin.IsMultiFileUploadArgsOk(args)
assert.Nil(t, err)
assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Trailing slash should be removed")
}
// Test Bug #3: URL Multiple Trailing Slashes - Multi File Upload
func TestIsMultiFileUploadArgsOk_MultipleTrailingSlashes(t *testing.T) {
args := Args{
EnvPluginInputArgs: EnvPluginInputArgs{
Username: "testUser",
Password: "testPass",
Protocol: "https",
ServerUrl: "nexus.example.com///",
NexusVersion: "nexus3",
Repository: "repo",
GroupId: "com.test",
Format: "maven2",
Artifact: "[{\"file\":\"test.jar\",\"artifactId\":\"app\",\"type\":\"jar\",\"version\":\"1.0\",\"groupId\":\"com.test\"}]",
},
}
plugin := NexusPlugin{}
err := plugin.IsMultiFileUploadArgsOk(args)
assert.Nil(t, err)
assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Multiple trailing slashes should be removed")
}
// Test Bug #3: URL No Trailing Slash - Multi File Upload (should remain unchanged)
func TestIsMultiFileUploadArgsOk_NoTrailingSlash(t *testing.T) {
args := Args{
EnvPluginInputArgs: EnvPluginInputArgs{
Username: "testUser",
Password: "testPass",
Protocol: "https",
ServerUrl: "nexus.example.com",
NexusVersion: "nexus3",
Repository: "repo",
GroupId: "com.test",
Format: "maven2",
Artifact: "[{\"file\":\"test.jar\",\"artifactId\":\"app\",\"type\":\"jar\",\"version\":\"1.0\",\"groupId\":\"com.test\"}]",
},
}
plugin := NexusPlugin{}
err := plugin.IsMultiFileUploadArgsOk(args)
assert.Nil(t, err)
assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "URL without trailing slash should remain unchanged")
}
// Test Bug #2: Filename extraction from absolute Linux path
func TestUploadFileNexus3_AbsolutePath_Linux(t *testing.T) {
mockClient := new(MockHttpClient)
mockResp := &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("Success")),
}
// Track what filename was actually sent in the multipart form
var capturedRequest *http.Request
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
capturedRequest = args.Get(0).(*http.Request)
}).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.Nil(t, err)
assert.NotNil(t, capturedRequest, "HTTP request should have been made")
// The request should have been made (we can't easily verify multipart content without parsing it)
mockClient.AssertExpectations(t)
}
// Test Bug #2: Filename extraction from absolute Windows-style path
func TestUploadFileNexus3_WindowsPath(t *testing.T) {
mockClient := new(MockHttpClient)
mockResp := &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("Success")),
}
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
// Simulate Windows-style path in artifact (actual file is tmpFile)
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.Nil(t, err)
mockClient.AssertExpectations(t)
}
// Test Bug #2: Relative path should also work
func TestUploadFileNexus3_RelativePath(t *testing.T) {
mockClient := new(MockHttpClient)
mockResp := &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("Success")),
}
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.Nil(t, err)
mockClient.AssertExpectations(t)
}
// Test Bug #1: Response Body Reading - 401 Error with Details
func TestUploadFileNexus3_ResponseBody_401(t *testing.T) {
mockClient := new(MockHttpClient)
fakeErrorBody := `{"errors":[{"id":"*","message":"Invalid credentials"}]}`
mockResp := &http.Response{
StatusCode: 401,
Body: ioutil.NopCloser(strings.NewReader(fakeErrorBody)),
}
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Invalid credentials", "Error should include response body details")
mockClient.AssertExpectations(t)
}
// Test Bug #1: Response Body Reading - 500 Error with Details
func TestUploadFileNexus3_ResponseBody_500(t *testing.T) {
mockClient := new(MockHttpClient)
fakeErrorBody := `{"errors":[{"id":"*","message":"Internal server error: Invalid field value"}]}`
mockResp := &http.Response{
StatusCode: 500,
Body: ioutil.NopCloser(strings.NewReader(fakeErrorBody)),
}
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Internal server error", "Error should include response body details")
mockClient.AssertExpectations(t)
}
// Test Bug #1: Response Body Reading - 404 Error with Details
func TestUploadFileNexus3_ResponseBody_404(t *testing.T) {
mockClient := new(MockHttpClient)
fakeErrorBody := `{"errors":[{"id":"*","message":"Repository not found"}]}`
mockResp := &http.Response{
StatusCode: 404,
Body: ioutil.NopCloser(strings.NewReader(fakeErrorBody)),
}
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Repository not found", "Error should include response body details")
mockClient.AssertExpectations(t)
}
// Test Bug #1: Response Body Reading - Success Response with Body
func TestUploadFileNexus3_ResponseBody_Success(t *testing.T) {
mockClient := new(MockHttpClient)
fakeSuccessBody := `{"success":true}`
mockResp := &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader(fakeSuccessBody)),
}
mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil)
tmpFile, err := createTempFile("test content")
assert.NoError(t, err)
defer os.Remove(tmpFile)
plugin := NexusPlugin{
PluginProcessingInfo: PluginProcessingInfo{
UserName: "testUser",
Password: "testPass",
ServerUrl: "https://nexus.example.com",
Repository: "repo",
Format: "maven2",
Version: "nexus3",
},
HttpClient: mockClient,
}
artifact := Artifact{
File: tmpFile,
ArtifactId: "test-app",
Type: "jar",
Version: "1.0",
GroupId: "com.test",
}
err = plugin.uploadFileNexus3(artifact, tmpFile)
assert.Nil(t, err, "Should succeed without error")
mockClient.AssertExpectations(t)
}