@@ -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
100106func (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