Skip to content

Commit 2f1e27d

Browse files
pmarchiniPietro Marchini
andauthored
feat: metadata to file upload request (#115)
* feat: add fields to multipart request * refactor: improve readability * refactor: housecleaning * chore: doc * feat: add errUnknownAssetType --------- Co-authored-by: Pietro Marchini <[email protected]>
1 parent ca85731 commit 2f1e27d

File tree

8 files changed

+176
-26
lines changed

8 files changed

+176
-26
lines changed

docs/30_commands.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ Available flags for the command:
275275

276276
View and manage Marketplace items
277277

278+
**WARNING: This command is a beta and may not be stable. Use at your own risk.**
279+
278280
All the subcommands inherit the following flags:
279281

280282
```
@@ -296,6 +298,8 @@ List Marketplace items
296298

297299
List the Marketplace items that the current user can access.
298300

301+
**WARNING: This command is a beta and may not be stable. Use at your own risk.**
302+
299303
```
300304
miactl marketplace list [flags]
301305
```
@@ -308,6 +312,8 @@ Get a Marketplace item
308312

309313
Get a single Marketplace item by its ID
310314

315+
**WARNING: This command is a beta and may not be stable. Use at your own risk.**
316+
311317
```
312318
miactl marketplace get resource-id [flags]
313319
```
@@ -320,6 +326,8 @@ Delete a Marketplace item
320326

321327
Delete a single Marketplace item by its ID
322328

329+
**WARNING: This command is a beta and may not be stable. Use at your own risk.**
330+
323331
```
324332
miactl marketplace delete resource-id [flags]
325333
```
@@ -332,6 +340,8 @@ Create or update Marketplace items
332340

333341
Create or update one or more Marketplace items.
334342

343+
**WARNING: This command is a beta and may not be stable. Use at your own risk.**
344+
335345
The flag -f accepts either files or directories. In case of directories, it explores them recursively.
336346

337347
Supported formats are JSON (.json files) and YAML (.yaml or .yml files).

internal/cmd/marketplace.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
func MarketplaceCmd(options *clioptions.CLIOptions) *cobra.Command {
2626
cmd := &cobra.Command{
2727
Use: "marketplace",
28-
Short: "View and manage Marketplace items",
28+
Short: "View and manage Marketplace items - beta",
2929
}
3030

3131
// add cmd flags

internal/cmd/marketplace/apply/apply.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ miactl marketplace apply -f myFantasticGoTemplates`
6363

6464
applyEndpointTemplate = "/api/backend/marketplace/tenants/%s/resources"
6565

66-
imageKey = "image"
67-
imageURLKey = "imageUrl"
66+
imageAssetType = "imageAssetType"
67+
imageKey = "image"
68+
imageURLKey = "imageUrl"
6869

69-
supportedByImageKey = "supportedByImage"
70-
supportedByImageURLKey = "supportedByImageUrl"
70+
supportedByImageAssetType = "supportedByImageAssetType"
71+
supportedByImageKey = "supportedByImage"
72+
supportedByImageURLKey = "supportedByImageUrl"
7173
)
7274

7375
var (
@@ -81,13 +83,14 @@ var (
8183
errResItemIDNotAString = errors.New(`the field "itemId" must be a string`)
8284
errInvalidExtension = errors.New("file has an invalid extension. Valid extensions are `.json`, `.yaml` and `.yml`")
8385
errDuplicatedResItemID = errors.New("some resources have duplicated itemId field")
86+
errUnknownAssetType = errors.New("unknown asset type")
8487
)
8588

8689
// ApplyCmd returns a new cobra command for adding or updating marketplace resources
8790
func ApplyCmd(options *clioptions.CLIOptions) *cobra.Command {
8891
cmd := &cobra.Command{
8992
Use: "apply { -f file-path }... }",
90-
Short: "Create or update Marketplace items",
93+
Short: "Create or update Marketplace items - beta",
9194
Long: applyLong,
9295
Example: applyExample,
9396
RunE: func(cmd *cobra.Command, args []string) error {
@@ -149,9 +152,18 @@ func concatPathDirToFilePathIfRelative(basePath, filePath string) string {
149152

150153
// processItemImages looks for image object and uploads the image when needed.
151154
// it processes image and supportedByImage, changing the object keys with respectively imageUrl and supportedByImageUrl after the upload
152-
func processItemImages(ctx context.Context, client *client.APIClient, companyID string, item *marketplace.Item, itemIDToFilePathMap map[string]string) error {
153-
processImage := func(objKey, urlKey string) error {
155+
func processItemImages(
156+
ctx context.Context,
157+
client *client.APIClient,
158+
companyID string,
159+
item *marketplace.Item,
160+
itemIDToFilePathMap map[string]string,
161+
) error {
162+
processImage := func(objKey, urlKey string, assetType string) error {
154163
localPath, err := getAndValidateImageLocalPath(item, objKey, urlKey)
164+
if assetType != imageAssetType && assetType != supportedByImageAssetType {
165+
return fmt.Errorf("%w: %s", errUnknownAssetType, assetType)
166+
}
155167
if err != nil {
156168
return err
157169
}
@@ -162,7 +174,14 @@ func processItemImages(ctx context.Context, client *client.APIClient, companyID
162174
itemFilePath := itemIDToFilePathMap[itemID]
163175
imageFilePath := concatPathDirToFilePathIfRelative(itemFilePath, localPath)
164176

165-
imageURL, err := uploadImageFileAndGetURL(ctx, client, companyID, imageFilePath)
177+
imageURL, err := uploadImageFileAndGetURL(
178+
ctx,
179+
client,
180+
companyID,
181+
imageFilePath,
182+
assetType,
183+
itemID,
184+
)
166185
if err != nil {
167186
return err
168187
}
@@ -172,10 +191,10 @@ func processItemImages(ctx context.Context, client *client.APIClient, companyID
172191
return nil
173192
}
174193

175-
if err := processImage(imageKey, imageURLKey); err != nil {
194+
if err := processImage(imageKey, imageURLKey, imageAssetType); err != nil {
176195
return err
177196
}
178-
err := processImage(supportedByImageKey, supportedByImageURLKey)
197+
err := processImage(supportedByImageKey, supportedByImageURLKey, supportedByImageAssetType)
179198
return err
180199
}
181200

@@ -271,7 +290,12 @@ func validateItemHumanReadableID(marketplaceItem *marketplace.Item, filePath str
271290
return itemIDStr, nil
272291
}
273292

274-
func applyMarketplaceResource(ctx context.Context, client *client.APIClient, companyID string, request *marketplace.ApplyRequest) (*marketplace.ApplyResponse, error) {
293+
func applyMarketplaceResource(
294+
ctx context.Context,
295+
client *client.APIClient,
296+
companyID string,
297+
request *marketplace.ApplyRequest,
298+
) (*marketplace.ApplyResponse, error) {
275299
if companyID == "" {
276300
return nil, errCompanyIDNotDefined
277301
}

internal/cmd/marketplace/apply/upload_image.go

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,25 +96,98 @@ func validateImageContentType(contentType string) error {
9696
return errFileMustBeImage
9797
}
9898

99-
func buildUploadImageReq(imageMimeType, fileName string, fileContents io.Reader) (string, []byte, error) {
99+
func appendFileToRequest(
100+
multipartWriter *multipart.Writer,
101+
fieldName,
102+
fileName,
103+
mimeType string,
104+
fileContents io.Reader,
105+
) error {
106+
formFileWriter, err := createFormFileWithContentType(multipartWriter, fieldName, fileName, mimeType)
107+
if err != nil {
108+
return err
109+
}
110+
111+
if _, err = io.Copy(formFileWriter, fileContents); err != nil {
112+
return err
113+
}
114+
115+
return nil
116+
}
117+
118+
func writeFormField(
119+
multipartWriter *multipart.Writer,
120+
fieldName,
121+
fieldValue string,
122+
) error {
123+
fieldWriter, err := multipartWriter.CreateFormField(fieldName)
124+
if err != nil {
125+
return err
126+
}
127+
128+
if _, err = fieldWriter.Write([]byte(fieldValue)); err != nil {
129+
return err
130+
}
131+
132+
return nil
133+
}
134+
135+
func writeMetadataFields(
136+
multipartWriter *multipart.Writer,
137+
itemID,
138+
assetType,
139+
companyID string,
140+
) error {
141+
if err := writeFormField(multipartWriter, "itemId", itemID); err != nil {
142+
return err
143+
}
144+
145+
if err := writeFormField(multipartWriter, "assetType", assetType); err != nil {
146+
return err
147+
}
148+
149+
err := writeFormField(multipartWriter, "tenantId", companyID)
150+
151+
return err
152+
}
153+
154+
func buildUploadImageReq(
155+
imageMimeType,
156+
fileName string,
157+
fileContents io.Reader,
158+
itemID,
159+
assetType,
160+
companyID string,
161+
) (string, []byte, error) {
100162
var bodyBuffer bytes.Buffer
101163
multipartWriter := multipart.NewWriter(&bodyBuffer)
102-
formFileWriter, err := createFormFileWithContentType(multipartWriter, multipartFieldName, fileName, imageMimeType)
103-
if err != nil {
164+
165+
if err := appendFileToRequest(multipartWriter, multipartFieldName, fileName, imageMimeType, fileContents); err != nil {
104166
return "", nil, err
105167
}
106-
if _, err = io.Copy(formFileWriter, fileContents); err != nil {
168+
169+
if err := writeMetadataFields(multipartWriter, itemID, assetType, companyID); err != nil {
170+
return "", nil, err
171+
}
172+
173+
if err := multipartWriter.Close(); err != nil {
107174
return "", nil, err
108175
}
109-
multipartWriter.Close()
110176

111177
reqContentType := multipartWriter.FormDataContentType()
112178
bodyBytes := bodyBuffer.Bytes()
113179

114180
return reqContentType, bodyBytes, nil
115181
}
116182

117-
func uploadImageFileAndGetURL(ctx context.Context, client *client.APIClient, companyID, filePath string) (string, error) {
183+
func uploadImageFileAndGetURL(
184+
ctx context.Context,
185+
client *client.APIClient,
186+
companyID,
187+
filePath string,
188+
assetType string,
189+
itemID string,
190+
) (string, error) {
118191
imageFile, err := os.Open(filePath)
119192
if err != nil {
120193
return "", err
@@ -133,7 +206,16 @@ func uploadImageFileAndGetURL(ctx context.Context, client *client.APIClient, com
133206
return "", err
134207
}
135208

136-
imageURL, err := uploadSingleFileWithMultipart(ctx, client, companyID, contentType, imageFile.Name(), imageFile)
209+
imageURL, err := uploadSingleFileWithMultipart(
210+
ctx,
211+
client,
212+
companyID,
213+
contentType,
214+
imageFile.Name(),
215+
imageFile,
216+
itemID,
217+
assetType,
218+
)
137219
if err != nil {
138220
return "", err
139221
}
@@ -142,12 +224,28 @@ func uploadImageFileAndGetURL(ctx context.Context, client *client.APIClient, com
142224

143225
// uploadSingleFileWithMultipart uploads the given Reader as a single multipart file
144226
// the part will also be given a filename and a contentType
145-
func uploadSingleFileWithMultipart(ctx context.Context, client *client.APIClient, companyID, fileMimeType, fileName string, fileContents io.Reader) (string, error) {
227+
func uploadSingleFileWithMultipart(
228+
ctx context.Context,
229+
client *client.APIClient,
230+
companyID,
231+
fileMimeType,
232+
fileName string,
233+
fileContents io.Reader,
234+
itemID string,
235+
assetType string,
236+
) (string, error) {
146237
if companyID == "" {
147238
return "", errCompanyIDNotDefined
148239
}
149240

150-
contentType, bodyBytes, err := buildUploadImageReq(fileMimeType, fileName, fileContents)
241+
contentType, bodyBytes, err := buildUploadImageReq(
242+
fileMimeType,
243+
fileName,
244+
fileContents,
245+
itemID,
246+
assetType,
247+
companyID,
248+
)
151249
if err != nil {
152250
return "", nil
153251
}

internal/cmd/marketplace/apply/upload_image_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,16 @@ func TestApplyUploadImage(t *testing.T) {
146146
client, err := client.APIClientForConfig(clientConfig)
147147
require.NoError(t, err)
148148

149-
found, err := uploadSingleFileWithMultipart(context.Background(), client, mockTenantID, "image/png", imageFile.Name(), imageFile)
149+
found, err := uploadSingleFileWithMultipart(
150+
context.Background(),
151+
client,
152+
mockTenantID,
153+
"image/png",
154+
imageFile.Name(),
155+
imageFile,
156+
"someItemId",
157+
"someAssetType",
158+
)
150159

151160
require.NoError(t, err)
152161
require.Equal(t, "https://example.org/image.png", found)
@@ -166,7 +175,16 @@ func TestApplyUploadImage(t *testing.T) {
166175
client, err := client.APIClientForConfig(clientConfig)
167176
require.NoError(t, err)
168177

169-
found, err := uploadSingleFileWithMultipart(context.Background(), client, "", "image/png", imageFile.Name(), imageFile)
178+
found, err := uploadSingleFileWithMultipart(
179+
context.Background(),
180+
client,
181+
"",
182+
"image/png",
183+
imageFile.Name(),
184+
imageFile,
185+
"someItemId",
186+
"someAssetType",
187+
)
170188
require.ErrorIs(t, err, errCompanyIDNotDefined)
171189
require.Zero(t, found)
172190
})

internal/cmd/marketplace/delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const (
3333
func DeleteCmd(options *clioptions.CLIOptions) *cobra.Command {
3434
cmd := &cobra.Command{
3535
Use: "delete resource-id",
36-
Short: "Delete Marketplace item",
36+
Short: "Delete Marketplace item - beta",
3737
Long: "Delete a single Marketplace item by its ID",
3838
Args: cobra.ExactArgs(1),
3939
SuggestFor: []string{"rm"},

internal/cmd/marketplace/get.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const (
3333
func GetCmd(options *clioptions.CLIOptions) *cobra.Command {
3434
cmd := &cobra.Command{
3535
Use: "get resource-id",
36-
Short: "Get Marketplace item",
36+
Short: "Get Marketplace item - beta",
3737
Long: `Get a single Marketplace item by its ID`,
3838
Args: cobra.MaximumNArgs(1),
3939
RunE: func(cmd *cobra.Command, args []string) error {

internal/cmd/marketplace/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const (
3535
func ListCmd(options *clioptions.CLIOptions) *cobra.Command {
3636
return &cobra.Command{
3737
Use: "list",
38-
Short: "List marketplace items",
38+
Short: "List marketplace items - beta",
3939
Long: `List the Marketplace items that the current user can access.`,
4040
RunE: func(cmd *cobra.Command, args []string) error {
4141
restConfig, err := options.ToRESTConfig()

0 commit comments

Comments
 (0)