Skip to content

Commit 596a2aa

Browse files
SuprajaVempalliSupraja Vempalli
andauthored
update the provider to allow multipartUpload (#22)
Signed-off-by: Supraja Vempalli <suprajavempalli@ibm.com> Co-authored-by: Supraja Vempalli <suprajavempalli@ibm.com>
1 parent 09ec501 commit 596a2aa

1 file changed

Lines changed: 107 additions & 24 deletions

File tree

internal/gdp/client.go

Lines changed: 107 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import (
99
"encoding/json"
1010
"fmt"
1111
"io"
12+
"mime/multipart"
1213
"net/http"
1314
"net/url"
15+
"os"
16+
"path/filepath"
1417

1518
"github.com/hashicorp/terraform-plugin-log/tflog"
1619
)
@@ -97,30 +100,106 @@ type ImportProfilesFromFileResponse struct {
97100
}
98101

99102
// ImportProfilesFromFile imports profiles from a file
103+
// Supports two methods:
104+
// 1. Multipart upload: If pathToFile exists locally, uploads file content via multipart/form-data
105+
// 2. Legacy SFTP: If pathToFile doesn't exist locally (server path), sends JSON with server path
100106
func (c *Client) ImportProfilesFromFile(ctx context.Context, httpClient *http.Client, accessToken, pathToFile string, updateMode bool) error {
101107
// Prepare the request URL
102108
importProfilesFromFileUrl := fmt.Sprintf("%s://%s:%s/restAPI/importProfilesFromFile", c.protocol, c.Host, c.port)
103109

104-
// Prepare the request body
105-
requestBody := ImportProfilesFromFileRequest{
106-
UpdateMode: updateMode,
107-
Path: pathToFile,
108-
}
110+
// Detect if this is a local file path or server path by checking if file exists locally
111+
_, err := os.Stat(pathToFile)
112+
isLocalFile := err == nil
109113

110-
jsonBody, err := json.Marshal(requestBody)
111-
if err != nil {
112-
return fmt.Errorf("error marshaling request body: %w", err)
113-
}
114+
var req *http.Request
114115

115-
// Create the request
116-
req, err := http.NewRequestWithContext(ctx, "POST", importProfilesFromFileUrl, bytes.NewBuffer(jsonBody))
117-
if err != nil {
118-
return fmt.Errorf("error creating request: %w", err)
119-
}
116+
if isLocalFile {
117+
// NEW METHOD: Multipart upload for local files
118+
tflog.Info(ctx, "Detected local file - using multipart upload", map[string]any{"pathToFile": pathToFile})
120119

121-
// Set headers
122-
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
123-
req.Header.Set("Content-Type", "application/json")
120+
file, err := os.Open(pathToFile)
121+
if err != nil {
122+
tflog.Error(ctx, "Error opening local file", map[string]any{"pathToFile": pathToFile, "error": err.Error()})
123+
return fmt.Errorf("error opening file %s: %w", pathToFile, err)
124+
}
125+
defer file.Close()
126+
127+
// Create multipart form data
128+
body := &bytes.Buffer{}
129+
writer := multipart.NewWriter(body)
130+
131+
// Add the file to the multipart form
132+
part, err := writer.CreateFormFile("path", filepath.Base(pathToFile))
133+
if err != nil {
134+
return fmt.Errorf("error creating form file: %w", err)
135+
}
136+
137+
_, err = io.Copy(part, file)
138+
if err != nil {
139+
return fmt.Errorf("error copying file content: %w", err)
140+
}
141+
142+
// Add updateMode parameter
143+
updateModeStr := "false"
144+
if updateMode {
145+
updateModeStr = "true"
146+
}
147+
err = writer.WriteField("updateMode", updateModeStr)
148+
if err != nil {
149+
return fmt.Errorf("error writing updateMode field: %w", err)
150+
}
151+
152+
// Add TestConnections parameter
153+
err = writer.WriteField("TestConnections", "false")
154+
if err != nil {
155+
return fmt.Errorf("error writing TestConnections field: %w", err)
156+
}
157+
158+
err = writer.Close()
159+
if err != nil {
160+
return fmt.Errorf("error closing multipart writer: %w", err)
161+
}
162+
163+
// Create the request
164+
req, err = http.NewRequestWithContext(ctx, "POST", importProfilesFromFileUrl, body)
165+
if err != nil {
166+
return fmt.Errorf("error creating request: %w", err)
167+
}
168+
169+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
170+
req.Header.Set("Content-Type", writer.FormDataContentType())
171+
172+
tflog.Info(ctx, "Sending multipart upload request", map[string]any{
173+
"url": importProfilesFromFileUrl,
174+
"contentType": writer.FormDataContentType(),
175+
})
176+
} else {
177+
// LEGACY METHOD: JSON API with server path (for SFTP)
178+
tflog.Info(ctx, "File not found locally - using legacy SFTP method with server path", map[string]any{"pathToFile": pathToFile})
179+
180+
requestBody := ImportProfilesFromFileRequest{
181+
UpdateMode: updateMode,
182+
Path: pathToFile,
183+
}
184+
185+
jsonBody, err := json.Marshal(requestBody)
186+
if err != nil {
187+
return fmt.Errorf("error marshaling request body: %w", err)
188+
}
189+
190+
req, err = http.NewRequestWithContext(ctx, "POST", importProfilesFromFileUrl, bytes.NewBuffer(jsonBody))
191+
if err != nil {
192+
return fmt.Errorf("error creating request: %w", err)
193+
}
194+
195+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
196+
req.Header.Set("Content-Type", "application/json")
197+
198+
tflog.Info(ctx, "Sending JSON request with server path", map[string]any{
199+
"url": importProfilesFromFileUrl,
200+
"serverPath": pathToFile,
201+
})
202+
}
124203

125204
// Send the request
126205
resp, err := httpClient.Do(req)
@@ -129,28 +208,32 @@ func (c *Client) ImportProfilesFromFile(ctx context.Context, httpClient *http.Cl
129208
}
130209

131210
defer resp.Body.Close()
132-
body, err := io.ReadAll(resp.Body)
211+
responseBody, err := io.ReadAll(resp.Body)
133212
if err != nil {
134213
return fmt.Errorf("error reading response body: %w", err)
135214
}
136215

216+
tflog.Info(ctx, "Received response", map[string]any{
217+
"statusCode": resp.StatusCode,
218+
"responseBody": string(responseBody),
219+
})
220+
137221
// Check the response status
138222
if resp.StatusCode != http.StatusOK {
139-
return fmt.Errorf("error response from server: %s, status code: %d", string(body), resp.StatusCode)
223+
return fmt.Errorf("error response from server: %s, status code: %d", string(responseBody), resp.StatusCode)
140224
}
141225

142-
// Parse the response body to check for errors in the Message field
226+
// Parse the response body to check for errors
143227
var apiResponse ImportProfilesFromFileResponse
144-
if err := json.Unmarshal(body, &apiResponse); err != nil {
228+
if err := json.Unmarshal(responseBody, &apiResponse); err != nil {
145229
tflog.Warn(ctx, "failed to parse import profiles response, continuing anyway: "+err.Error())
146-
tflog.Debug(ctx, "sent request to import profiles from file response "+string(body))
230+
tflog.Debug(ctx, "sent request to import profiles from file response "+string(responseBody))
147231
return nil
148232
}
149233

150-
tflog.Debug(ctx, "sent request to import profiles from file response "+string(body))
234+
tflog.Debug(ctx, "sent request to import profiles from file response "+string(responseBody))
151235

152236
// Check if the Message field contains an error
153-
// The API returns ID="0" for both success and failure, so we must check the Message field
154237
if apiResponse.Message != "" && containsErrorKeywords(apiResponse.Message) {
155238
return fmt.Errorf("import profiles failed: %s", apiResponse.Message)
156239
}

0 commit comments

Comments
 (0)