Repository: osbuild/osbuild-composer
Pull request #1912 (2021-10-13 13:27:58)

cloudapi/v2: ensure only one image per a compose in the API spec
We've never had the ability to build multiple images per a compose, this
commit thus rips out support for this on the API level:

image_requests is now image_request and it accepts only one ImageRequest
object instead of an array of them.

This pull request includes:

- [x] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] create a file in [news/unreleased](https://github.com/osbuild/osbuild-composer/tree/main/docs/news/unreleased) directory if this change should be mentioned in the release news
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

For user-visible changes, "adequate documentation" is an entry describing the
change for users in docs/news. Please refer to docs/news/README.md for details.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`
-->



Patch:
diff --git a/NEWS.md b/NEWS.md
index e97f494bb..2ac2442e2 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,15 @@
+## CHANGES WITH 36:
+
+  * Add support for official RHEL EC2 SAP image on RHEL-9.0
+  * Add support for official RHEL EC2 SAP image on RHEL-8.6
+  * Add support for RHEL 8.6
+
+Contributions from: Achilleas Koutsou, Diaa Sami, Jakub Rusz, Martin Sehnoutka,
+                    Ondřej Budai, Sanne Raymaekers, Simon Steinbeiss,
+                    Tom Gundersen, Tomas Hozza,
+
+— Berlin, 2021-10-13
+
 ## CHANGES WITH 35:
 
   * Change filesystem minsize from int, which is platform dependent, to uint64
diff --git a/docs/news/unreleased/rhel-9.0-ec2-sap-image.md b/docs/news/36/rhel-9.0-ec2-sap-image.md
similarity index 100%
rename from docs/news/unreleased/rhel-9.0-ec2-sap-image.md
rename to docs/news/36/rhel-9.0-ec2-sap-image.md
diff --git a/docs/news/unreleased/rhel8.6-ec2-sap-image.md b/docs/news/36/rhel8.6-ec2-sap-image.md
similarity index 100%
rename from docs/news/unreleased/rhel8.6-ec2-sap-image.md
rename to docs/news/36/rhel8.6-ec2-sap-image.md
diff --git a/docs/news/unreleased/rhel8.6-support.md b/docs/news/36/rhel8.6-support.md
similarity index 100%
rename from docs/news/unreleased/rhel8.6-support.md
rename to docs/news/36/rhel8.6-support.md
diff --git a/internal/cloudapi/v2/errors.go b/internal/cloudapi/v2/errors.go
index 5a3c7f460..fa73fe3f0 100644
--- a/internal/cloudapi/v2/errors.go
+++ b/internal/cloudapi/v2/errors.go
@@ -24,7 +24,6 @@ const (
 	ErrorInvalidOSTreeRef        ServiceErrorCode = 9
 	ErrorInvalidOSTreeRepo       ServiceErrorCode = 10
 	ErrorFailedToMakeManifest    ServiceErrorCode = 11
-	ErrorMultiImageCompose       ServiceErrorCode = 13
 	ErrorInvalidComposeId        ServiceErrorCode = 14
 	ErrorComposeNotFound         ServiceErrorCode = 15
 	ErrorInvalidErrorId          ServiceErrorCode = 16
@@ -84,7 +83,6 @@ func getServiceErrors() serviceErrors {
 		serviceError{ErrorInvalidOSTreeRef, http.StatusBadRequest, "Invalid OSTree ref"},
 		serviceError{ErrorInvalidOSTreeRepo, http.StatusBadRequest, "Error resolving OSTree repo"},
 		serviceError{ErrorFailedToMakeManifest, http.StatusBadRequest, "Failed to get manifest"},
-		serviceError{ErrorMultiImageCompose, http.StatusBadRequest, "Only single-image composes are currently supported"},
 		serviceError{ErrorInvalidComposeId, http.StatusBadRequest, "Invalid format for compose id"},
 		serviceError{ErrorComposeNotFound, http.StatusNotFound, "Compose with given id not found"},
 		serviceError{ErrorInvalidErrorId, http.StatusBadRequest, "Invalid format for error id, it should be an integer as a string"},
diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go
index a19dfa2ad..07225e93c 100644
--- a/internal/cloudapi/v2/openapi.v2.gen.go
+++ b/internal/cloudapi/v2/openapi.v2.gen.go
@@ -96,7 +96,7 @@ type ComposeRequest struct {
 	// Embedded fields due to inline allOf schema
 	Customizations *Customizations `json:"customizations,omitempty"`
 	Distribution   string          `json:"distribution"`
-	ImageRequests  []ImageRequest  `json:"image_requests"`
+	ImageRequest   ImageRequest    `json:"image_request"`
 }
 
 // ComposeStatus defines model for ComposeStatus.
@@ -478,65 +478,65 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"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",
+	"H4sIAAAAAAAC/+xbe28bOZL/KkTvAZ7BdestxxYwmHUcb9Z7kziwnFncxYZBdZfU3HSTHZJtWQ703Q98",
+	"dKsf1MM7nlkM4H8SSSSrflWsKlYV6e9eyNKMUaBSeJPvXoY5TkECt98WoP6PQIScZJIw6k28T3gBiNAI",
+	"Hj3fg0ecZgnUpj/gJAdv4vW99dr3iFrzLQe+8nyP4lSN6Jm+J8IYUqyWyFWmfheSE7rQywR5cvD+mKcz",
+	"4IjNEZGQCkQoAhzGyBKsoikIlGh6va149NxdeNbFoCZ99s/pxfngc5YwHF1paEZ+zjLgkhj+HBYa8/cC",
+	"lTfxIA+WIGTQ9/wmC98TMeZwvyQyvsdhyHK7JeXqL15/MByNj9+cnPb6A+/O97QOHHBL4phzvNK0Kc5E",
+	"zOS9EbiKKV0FxWgb1dr3OHzLCYdIAbAyubHelavZ7F8QSsW3qqmpxDJ3KAqnpI4IpyTohSfD3pvT4Zs3",
+	"4/HpOBrNXBp7poobwii+JY0t4KfDl91ltz73MN+muJwnbt+pslCTnPSfcg57hCMpXkBpMg1PxCkoP5Qx",
+	"oFyTgQjpBR10KVGaC4lmgHJKvuUqXOiJC/IAFHEQLOchoAVneda5pZdzpJggIhBLiZQQoTlnqV6iZAEh",
+	"fYQRxzRiKWIU0AwLiBCjCKPPny/fISJu6QIocCwh6tzSTSwwFq6BuUwoYSGWdgfrAv5iR9AyBg4ai6aC",
+	"RMzyJNLCFXJjGiG1l0IC1/z/zpZIMpQQIRFOElSwEZNbGkuZiUm3G7FQdFIScibYXHZClnaBBrnohgnp",
+	"YrU9XetbPz8QWP6kfwrChAQJliDkX/BT4Xz3itF9yeSooQBljZCrrXV7kdmOe70du3e6vnUHqKa5Fzcs",
+	"DzG9tmTea46uWJjPSgj3JGqDunynIFWn/RtgRjCOTmaDMMCzwSgYjfrD4LQXjoPj/mDYO4aT3ikMXOgk",
+	"UEzlDlwKhJl0GCprLnNCI0Rk4S3aRdEnxiVODrGbwmYkeYAgIhxCyfiqO89phFOgEieiNRrEbBlIFijW",
+	"gYHcUNI4fAPz8ew46IfDeTCKcC/Ax4NB0Jv1jnuD4Wn0JnqzN9BtNNbe25YFVrxyT+TaFhnrgeuQSNDA",
+	"WyHggnCukiYBl9oAcJJczb3Jl+/ef3GYexPvL91NUtW1aUP3Si++hjlwoCF4a78FOqqD7Q+GoI77AE5O",
+	"Z0F/EA0DPBofB6PB8fF4PBr1er2e53tzxlMsvYmX51qZewSLHALdbUT6ABJHWOKXFIwJyQHuQ5amRDpd",
+	"5ocYi/jHwnNmOUkkstMd7pfh8CteGNrN1FSPmLhLaJjkEaEL9PHi1+szr5Iv7ZLH0igV0cqm1rv0d22O",
+	"q5dUX5gLyVLyhMtTehe98/rste9FRKlulstWosJjSIITl4qN/fONMLtYXqrJheBNg6txbxLeaYob534x",
+	"D9PMRUl3r1AWgjs6WDpbZGhtWh1K1YYrGX7GhFxwEM/M7isRdZ9c0+rcte/lwhZ7BznHZwH8EI/wvQvO",
+	"GX9RN2AROLWhJuFKouBIcLAwitkdGzWHcnqDsHubtZS/kOc4vJ7tsM1C/Qftg9GuayNqdqpJuZG/P/+0",
+	"J/uf5eFXkNvzQUwRPBIhVYSd3px9fHd2/Q5NJeMqAocJFgK91SQ6zWzcfgksh63xx1153MRgygXJUC4A",
+	"zRm3+VXGuLTZuC5QI6RCSS4BXdAFoTYF69zSmzId04QaxYoqa20K9v78E8o4U2rz0TImYayKlFxAdEsL",
+	"vldTS8skdJq9wdJBqrJhEokMQjInCputYm7pUWjCHA9wRoLbvNcbhuoI15/gCBllFOwQFpUkUqF+TpWz",
+	"qVLbqlQimvFKrlrKtCRJolRTKleyqn5VmWb1qfsspSqx+k4iTb3I5jpoCoCKNDZMWB51FowtEtBJrDCm",
+	"o/PbblnL2PKwqkRfQ0zzRJLAIi+mozBhAoRUMNUkk1fe0h9s2VKYpzHMctmPSs1hzARQhHPJUixJiJNk",
+	"1VQy5M/o3DTqSZWTsHmhFy03KqYrvJpK3ZJd5qvNs3NLL3AYF0aitR4yKjFRJXGhKV5kVJYNUsg76FeN",
+	"wOSNAmEOk1uKUICO1Fkw+Q4pJgmJ1kcTdEaR/oZwFHEQygSxRBwyDkLFow2vUJFADbE66G+MI6s9Hx3h",
+	"hITwV/td7flRx3IWwB9ICGdm3TMxGNaWxDbe6SpgMtbelv0VZ5nImOws7KJiTRWSrkWeqw0rf9HYULga",
+	"KohSQoVTBxFLMaGT7+Z/xVC7J5rmRAIyv6IfMk5SzFc/tpkniWGoOzLqVDe7j6Vd29TIxvWOEOPoqIHJ",
+	"7XW7TZMIs8YEB2WoCNPVLS30W/emLzr5mLSsQtWIdXs4dPM83zPb1laz53tWwdUfn5FmbWuF2kPMVSaW",
+	"Z+zL1am+Z4+j+2a5iEUINMJUBjOOSRQMe8Nxf7i3IKyQ8/eVvbVEv93H5WFMJIQy5w1xHk+O749H2895",
+	"8/MB+fjNKgNd05iSct+aq+mNmqUlzpggkvFmtrVr+XWxaOVKus1pf8+yg8qyeq7VakVXVVfTSgN6i+1d",
+	"sS3bTOzZ1c6v+r5kI+BhBGp23hSvqJTqWA0jZSg0T/W0PAxBKCHnmCRGFRlQVcJrPyOJ/WiQmc9F21V9",
+	"u3NYWMVuKqzwUrFZhJnne7ptpuJStICg7Drob4QKiZMEuJN0kffXFf6VUHcZUlyP2QFCJSxMNVVcVbVH",
+	"JJM4cQ01NKyZ+uW9mrnOMov9rWWA71kHcdxqzNudgu5J1zhyV+nG5c1bLyTajBvlXgtBbCG0I4ZbuVu0",
+	"3u5++YWuNAeXUpoNIGegc4KAjG0ZKUK8IzNPAAv3mCCLNBpvG6K4CLRbDi7HwANwQQ4phW3s0bA3yzZw",
+	"faOEEqNy7Uq4bNeSWIC1jo1RlZVARDscohibZrbKY4HKbkSE7CrDO9lYnqLDRJeJbq3zyROXOaYgcULo",
+	"VzfXlKgaWnTmEDGO7THYYXzRLdb9rELvT2Y8GA5UYTY4VnL/VB5oeyFoJokNFHUQJQY13AmBSiY0/5+t",
+	"ln86CdQxh9MKZ6z+PR6ZXzS+t1jA1fQALDwWaWXnZ4wlgGk7sVHTXH4xbXSZGk4RSvJguiVfYdW+YoaQ",
+	"gwzUUAVphoVYMh654KqtvnfaTNtkDpCeUEEWceNKXfIc/JZCfI/xBaa2eVfnP+iNesOBM5dR6SjwNuRq",
+	"d66jtFtBvjc9qyHxm1quMa2orCKuaydbjR9G4YDOlevZw9rfu6Z5h75vSasztZdH+ypbt7h2597st4hf",
+	"JDqHS3/gimbJ8AzZixVK9E3SdlhyxXNKt2VQh2TnBoFNz93Zn18cKtXUtbqulZ7hpeiIYSNPcyHUXekX",
+	"bDXrGrJeJ2zcWQ86H/A0K4RWHBQiDiAajMf9U3R2dnZ2Pvz4hM/7yf+9u+x/vLkYq98uP/L3/3PBP/wv",
+	"+e8PHz4v87/j67N/pNe/sMun6/ng27tB9G781Ht789g9fnSBaBeTqsje/xRlS9F3p58+QZhzIldTpUGj",
+	"oreAuVH6TH/6WxF+//HPm+IllQ6qZl5JV8Vv856K0Dlrd8mmtosjmb4GtN1Uk4abJoPoeL6XkBCoSZvs",
+	"E66zDIcxoEGn59lEtDzpl8tlB+thfbzataL7y+X5xcfpRTDo9DqxTBO9h0RqpV1N32r29i6KI92uRDgj",
+	"lXxo4g3sBQRVAxNv2Ol1+joPl7FWU9c2eXX4YcLRTT/ngCUgjCgskZ3to4ypFIjgJFmhkFFh2+xsjgQ8",
+	"AMeFLrR6bN9ZP4QzfU/CUQRqie2hVi8zLiNv4n1iQlrRPGMHIORbFq3MTYtOwLRHZVlCTI+0+y97ibJ5",
+	"JbfzHrJ+H7qu25s6eM3Tk4ypvVDUBr3+S3O/jAzjhsrNIIqxQEJiLiFS2zjq9V6Mv72fafO+pKb/a3e6",
+	"eN5k+Pd/f/5nuVRG8hUoIgIRg8ZwH/7+3D9TnMuYcfJkbhIy4CpvQ6VxGiSjPwLJV8qWtNwHo4TxH2EC",
+	"nyk8ZhBKiBCoOYiFYc6VW1RjrT7Giij75W5953siT1OsqqsiaBTBRa0rIk33O4nW+hBz3d29B2nuRfSZ",
+	"rG/xkD37EeOaYAIKmaWm73a0oYRJHoFAyxhkDFxNpszQKlSoMwyIIGqHm/cg65f6fu2l8Rf3K6qSsAEr",
+	"GVro20L9gleF2M0DXvuMqBpeqs95X/xRzV0rdvVeOnaVvbSWAdX18h8LXUXceI1ar1HroKh10wg8W8OX",
+	"7sAUvbedcayYaAjOCSUibkQvQPCIQ4lUvqmcmjCKOMicU4hQBKoGEojR6mPj4iWzuS/dEc3KHuFrPNsb",
+	"zzYP6trGdVPdyuJdhXksXmzla5h7DXN/jjDXik3KoHHFkFW408RFJb61QszmaVkruLgk20zp6lugbY2j",
+	"yjx9TfS7uv5GBpe1m2e6bI6sMl7d7D/jZsbQ/3xOhksDwkmCMiYEmSVQWtPGzfbXRJiaJhMNyz91Mcg2",
+	"L/dmK6SPTrejHpYBlHR/66k//IPP8HIrX3301Uef46NmbZW09suyZbr9/LuyU9xWXQdryWlvRYQipQP7",
+	"wPHPmDnsFGddXjWaOFPvdeOMdNRyERP7t2E4I11dzQS6oQ48KB4edx8GXlOKD/aRIYvy0LyMNbx0PtFm",
+	"JSRewG9iOJV4QeiizeaZdLSuafHW0Vvfrf8/AAD//8f1mrXcPgAA",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml
index a01574c26..64f7f2d84 100644
--- a/internal/cloudapi/v2/openapi.v2.yml
+++ b/internal/cloudapi/v2/openapi.v2.yml
@@ -474,15 +474,13 @@ components:
       - type: object
         required:
           - distribution
-          - image_requests
+          - image_request
         properties:
           distribution:
             type: string
             example: 'rhel-8'
-          image_requests:
-            type: array
-            items:
-              $ref: '#/components/schemas/ImageRequest'
+          image_request:
+            $ref: '#/components/schemas/ImageRequest'
           customizations:
             $ref: '#/components/schemas/Customizations'
     ImageRequest:
diff --git a/internal/cloudapi/v2/v2.go b/internal/cloudapi/v2/v2.go
index 75bf804e0..bb7ce6e98 100644
--- a/internal/cloudapi/v2/v2.go
+++ b/internal/cloudapi/v2/v2.go
@@ -160,13 +160,12 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 		}
 	}
 
-	type imageRequest struct {
+	var imageRequest struct {
 		manifest distro.Manifest
 		arch     string
 		exports  []string
+		target   *target.Target
 	}
-	imageRequests := make([]imageRequest, len(request.ImageRequests))
-	var targets []*target.Target
 
 	// use the same seed for all images so we get the same IDs
 	bigSeed, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
@@ -175,257 +174,248 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 	}
 	manifestSeed := bigSeed.Int64()
 
-	for i, ir := range request.ImageRequests {
-		arch, err := distribution.GetArch(ir.Architecture)
-		if err != nil {
-			return HTTPError(ErrorUnsupportedArchitecture)
-		}
-		imageType, err := arch.GetImageType(imageTypeFromApiImageType(ir.ImageType))
-		if err != nil {
-			return HTTPError(ErrorUnsupportedImageType)
-		}
-		repositories := make([]rpmmd.RepoConfig, len(ir.Repositories))
-		for j, repo := range ir.Repositories {
-			repositories[j].RHSM = repo.Rhsm
-
-			if repo.Baseurl != nil {
-				repositories[j].BaseURL = *repo.Baseurl
-			} else if repo.Mirrorlist != nil {
-				repositories[j].MirrorList = *repo.Mirrorlist
-			} else if repo.Metalink != nil {
-				repositories[j].Metalink = *repo.Metalink
-			} else {
-				return HTTPError(ErrorInvalidRepository)
-			}
+	ir := request.ImageRequest
+	arch, err := distribution.GetArch(ir.Architecture)
+	if err != nil {
+		return HTTPError(ErrorUnsupportedArchitecture)
+	}
+	imageType, err := arch.GetImageType(imageTypeFromApiImageType(ir.ImageType))
+	if err != nil {
+		return HTTPError(ErrorUnsupportedImageType)
+	}
+	repositories := make([]rpmmd.RepoConfig, len(ir.Repositories))
+	for j, repo := range ir.Repositories {
+		repositories[j].RHSM = repo.Rhsm
+
+		if repo.Baseurl != nil {
+			repositories[j].BaseURL = *repo.Baseurl
+		} else if repo.Mirrorlist != nil {
+			repositories[j].MirrorList = *repo.Mirrorlist
+		} else if repo.Metalink != nil {
+			repositories[j].Metalink = *repo.Metalink
+		} else {
+			return HTTPError(ErrorInvalidRepository)
 		}
+	}
 
-		packageSets := imageType.PackageSets(bp)
-		depsolveJobID, err := h.server.workers.EnqueueDepsolve(&worker.DepsolveJob{
-			PackageSets:      packageSets,
-			Repos:            repositories,
-			ModulePlatformID: distribution.ModulePlatformID(),
-			Arch:             arch.Name(),
-			Releasever:       distribution.Releasever(),
-		})
+	packageSets := imageType.PackageSets(bp)
+	depsolveJobID, err := h.server.workers.EnqueueDepsolve(&worker.DepsolveJob{
+		PackageSets:      packageSets,
+		Repos:            repositories,
+		ModulePlatformID: distribution.ModulePlatformID(),
+		Arch:             arch.Name(),
+		Releasever:       distribution.Releasever(),
+	})
+	if err != nil {
+		return HTTPErrorWithInternal(ErrorEnqueueingJob, err)
+	}
+
+	var depsolveResults worker.DepsolveJobResult
+	for {
+		status, _, err := h.server.workers.JobStatus(depsolveJobID, &depsolveResults)
 		if err != nil {
-			return HTTPErrorWithInternal(ErrorEnqueueingJob, err)
+			return HTTPErrorWithInternal(ErrorGettingDepsolveJobStatus, err)
 		}
-
-		var depsolveResults worker.DepsolveJobResult
-		for {
-			status, _, err := h.server.workers.JobStatus(depsolveJobID, &depsolveResults)
-			if err != nil {
-				return HTTPErrorWithInternal(ErrorGettingDepsolveJobStatus, err)
-			}
-			if status.Canceled {
-				return HTTPErrorWithInternal(ErrorDepsolveJobCanceled, err)
-			}
-			if !status.Finished.IsZero() {
-				break
-			}
-			time.Sleep(50 * time.Millisecond)
+		if status.Canceled {
+			return HTTPErrorWithInternal(ErrorDepsolveJobCanceled, err)
+		}
+		if !status.Finished.IsZero() {
+			break
 		}
+		time.Sleep(50 * time.Millisecond)
+	}
 
-		if depsolveResults.Error != "" {
-			if depsolveResults.ErrorType == worker.DepsolveErrorType {
-				return HTTPError(ErrorDNFError)
-			}
-			return HTTPErrorWithInternal(ErrorFailedToDepsolve, errors.New(depsolveResults.Error))
+	if depsolveResults.Error != "" {
+		if depsolveResults.ErrorType == worker.DepsolveErrorType {
+			return HTTPError(ErrorDNFError)
 		}
+		return HTTPErrorWithInternal(ErrorFailedToDepsolve, errors.New(depsolveResults.Error))
+	}
 
-		pkgSpecSets := depsolveResults.PackageSpecs
+	pkgSpecSets := depsolveResults.PackageSpecs
 
-		imageOptions := distro.ImageOptions{Size: imageType.Size(0)}
-		if request.Customizations != nil && request.Customizations.Subscription != nil {
-			imageOptions.Subscription = &distro.SubscriptionImageOptions{
-				Organization:  request.Customizations.Subscription.Organization,
-				ActivationKey: request.Customizations.Subscription.ActivationKey,
-				ServerUrl:     request.Customizations.Subscription.ServerUrl,
-				BaseUrl:       request.Customizations.Subscription.BaseUrl,
-				Insights:      request.Customizations.Subscription.Insights,
-			}
+	imageOptions := distro.ImageOptions{Size: imageType.Size(0)}
+	if request.Customizations != nil && request.Customizations.Subscription != nil {
+		imageOptions.Subscription = &distro.SubscriptionImageOptions{
+			Organization:  request.Customizations.Subscription.Organization,
+			ActivationKey: request.Customizations.Subscription.ActivationKey,
+			ServerUrl:     request.Customizations.Subscription.ServerUrl,
+			BaseUrl:       request.Customizations.Subscription.BaseUrl,
+			Insights:      request.Customizations.Subscription.Insights,
 		}
+	}
 
-		// set default ostree ref, if one not provided
-		ostreeOptions := ir.Ostree
-		if ostreeOptions == nil || ostreeOptions.Ref == nil {
-			imageOptions.OSTree = distro.OSTreeImageOptions{Ref: imageType.OSTreeRef()}
-		} else if !ostree.VerifyRef(*ostreeOptions.Ref) {
-			return HTTPError(ErrorInvalidOSTreeRef)
-		} else {
-			imageOptions.OSTree = distro.OSTreeImageOptions{Ref: *ostreeOptions.Ref}
+	// set default ostree ref, if one not provided
+	ostreeOptions := ir.Ostree
+	if ostreeOptions == nil || ostreeOptions.Ref == nil {
+		imageOptions.OSTree = distro.OSTreeImageOptions{Ref: imageType.OSTreeRef()}
+	} else if !ostree.VerifyRef(*ostreeOptions.Ref) {
+		return HTTPError(ErrorInvalidOSTreeRef)
+	} else {
+		imageOptions.OSTree = distro.OSTreeImageOptions{Ref: *ostreeOptions.Ref}
+	}
+
+	var parent string
+	if ostreeOptions != nil && ostreeOptions.Url != nil {
+		imageOptions.OSTree.URL = *ostreeOptions.Url
+		parent, err = ostree.ResolveRef(imageOptions.OSTree.URL, imageOptions.OSTree.Ref)
+		if err != nil {
+			return HTTPErrorWithInternal(ErrorInvalidOSTreeRepo, err)
 		}
+		imageOptions.OSTree.Parent = parent
+	}
 
-		var parent string
-		if ostreeOptions != nil && ostreeOptions.Url != nil {
-			imageOptions.OSTree.URL = *ostreeOptions.Url
-			parent, err = ostree.ResolveRef(imageOptions.OSTree.URL, imageOptions.OSTree.Ref)
-			if err != nil {
-				return HTTPErrorWithInternal(ErrorInvalidOSTreeRepo, err)
-			}
-			imageOptions.OSTree.Parent = parent
-		}
-
-		// Set the blueprint customisation to take care of the user
-		var blueprintCustoms *blueprint.Customizations
-		if request.Customizations != nil && request.Customizations.Users != nil {
-			var userCustomizations []blueprint.UserCustomization
-			for _, user := range *request.Customizations.Users {
-				var groups []string
-				if user.Groups != nil {
-					groups = *user.Groups
-				} else {
-					groups = nil
-				}
-				userCustomizations = append(userCustomizations,
-					blueprint.UserCustomization{
-						Name:   user.Name,
-						Key:    user.Key,
-						Groups: groups,
-					},
-				)
-			}
-			blueprintCustoms = &blueprint.Customizations{
-				User: userCustomizations,
+	// Set the blueprint customisation to take care of the user
+	var blueprintCustoms *blueprint.Customizations
+	if request.Customizations != nil && request.Customizations.Users != nil {
+		var userCustomizations []blueprint.UserCustomization
+		for _, user := range *request.Customizations.Users {
+			var groups []string
+			if user.Groups != nil {
+				groups = *user.Groups
+			} else {
+				groups = nil
 			}
+			userCustomizations = append(userCustomizations,
+				blueprint.UserCustomization{
+					Name:   user.Name,
+					Key:    user.Key,
+					Groups: groups,
+				},
+			)
 		}
-
-		manifest, err := imageType.Manifest(blueprintCustoms, imageOptions, repositories, pkgSpecSets, manifestSeed)
-		if err != nil {
-			return HTTPErrorWithInternal(ErrorFailedToMakeManifest, err)
+		blueprintCustoms = &blueprint.Customizations{
+			User: userCustomizations,
 		}
+	}
 
-		imageRequests[i].manifest = manifest
-		imageRequests[i].arch = arch.Name()
-		imageRequests[i].exports = imageType.Exports()
-
-		/* oneOf is not supported by the openapi generator so marshal and unmarshal the uploadrequest based on the type */
-		switch ir.ImageType {
-		case ImageTypes_aws:
-			var awsUploadOptions AWSEC2UploadOptions
-			jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONMarshallingError)
-			}
-			err = json.Unmarshal(jsonUploadOptions, &awsUploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONUnMarshallingError)
-			}
+	manifest, err := imageType.Manifest(blueprintCustoms, imageOptions, repositories, pkgSpecSets, manifestSeed)
+	if err != nil {
+		return HTTPErrorWithInternal(ErrorFailedToMakeManifest, err)
+	}
 
-			key := fmt.Sprintf("composer-api-%s", uuid.New().String())
-			t := target.NewAWSTarget(&target.AWSTargetOptions{
-				Filename:          imageType.Filename(),
-				Region:            awsUploadOptions.Region,
-				Bucket:            h.server.awsBucket,
-				Key:               key,
-				ShareWithAccounts: awsUploadOptions.ShareWithAccounts,
-			})
-			if awsUploadOptions.SnapshotName != nil {
-				t.ImageName = *awsUploadOptions.SnapshotName
-			} else {
-				t.ImageName = key
-			}
+	imageRequest.manifest = manifest
+	imageRequest.arch = arch.Name()
+	imageRequest.exports = imageType.Exports()
 
-			targets = append(targets, t)
-		case ImageTypes_edge_installer:
-			fallthrough
-		case ImageTypes_edge_commit:
-			var awsS3UploadOptions AWSS3UploadOptions
-			jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONMarshallingError)
-			}
-			err = json.Unmarshal(jsonUploadOptions, &awsS3UploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONUnMarshallingError)
-			}
+	/* oneOf is not supported by the openapi generator so marshal and unmarshal the uploadrequest based on the type */
+	switch ir.ImageType {
+	case ImageTypes_aws:
+		var awsUploadOptions AWSEC2UploadOptions
+		jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONMarshallingError)
+		}
+		err = json.Unmarshal(jsonUploadOptions, &awsUploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONUnMarshallingError)
+		}
 
-			key := fmt.Sprintf("composer-api-%s", uuid.New().String())
-			t := target.NewAWSS3Target(&target.AWSS3TargetOptions{
-				Filename: imageType.Filename(),
-				Region:   awsS3UploadOptions.Region,
-				Bucket:   h.server.awsBucket,
-				Key:      key,
-			})
+		key := fmt.Sprintf("composer-api-%s", uuid.New().String())
+		t := target.NewAWSTarget(&target.AWSTargetOptions{
+			Filename:          imageType.Filename(),
+			Region:            awsUploadOptions.Region,
+			Bucket:            h.server.awsBucket,
+			Key:               key,
+			ShareWithAccounts: awsUploadOptions.ShareWithAccounts,
+		})
+		if awsUploadOptions.SnapshotName != nil {
+			t.ImageName = *awsUploadOptions.SnapshotName
+		} else {
 			t.ImageName = key
+		}
 
-			targets = append(targets, t)
-		case ImageTypes_gcp:
-			var gcpUploadOptions GCPUploadOptions
-			jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONMarshallingError)
-			}
-			err = json.Unmarshal(jsonUploadOptions, &gcpUploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONUnMarshallingError)
-			}
+		imageRequest.target = t
+	case ImageTypes_edge_installer:
+		fallthrough
+	case ImageTypes_edge_commit:
+		var awsS3UploadOptions AWSS3UploadOptions
+		jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONMarshallingError)
+		}
+		err = json.Unmarshal(jsonUploadOptions, &awsS3UploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONUnMarshallingError)
+		}
 
-			var share []string
-			if gcpUploadOptions.ShareWithAccounts != nil {
-				share = *gcpUploadOptions.ShareWithAccounts
-			}
+		key := fmt.Sprintf("composer-api-%s", uuid.New().String())
+		t := target.NewAWSS3Target(&target.AWSS3TargetOptions{
+			Filename: imageType.Filename(),
+			Region:   awsS3UploadOptions.Region,
+			Bucket:   h.server.awsBucket,
+			Key:      key,
+		})
+		t.ImageName = key
 
-			object := fmt.Sprintf("composer-api-%s", uuid.New().String())
-			t := target.NewGCPTarget(&target.GCPTargetOptions{
-				Filename:          imageType.Filename(),
-				Region:            gcpUploadOptions.Region,
-				Os:                "", // not exposed in cloudapi for now
-				Bucket:            gcpUploadOptions.Bucket,
-				Object:            object,
-				ShareWithAccounts: share,
-			})
-			// Import will fail if an image with this name already exists
-			if gcpUploadOptions.ImageName != nil {
-				t.ImageName = *gcpUploadOptions.ImageName
-			} else {
-				t.ImageName = object
-			}
+		imageRequest.target = t
+	case ImageTypes_gcp:
+		var gcpUploadOptions GCPUploadOptions
+		jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONMarshallingError)
+		}
+		err = json.Unmarshal(jsonUploadOptions, &gcpUploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONUnMarshallingError)
+		}
 
-			targets = append(targets, t)
-		case ImageTypes_azure:
-			var azureUploadOptions AzureUploadOptions
-			jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONMarshallingError)
-			}
-			err = json.Unmarshal(jsonUploadOptions, &azureUploadOptions)
-			if err != nil {
-				return HTTPError(ErrorJSONUnMarshallingError)
-			}
-			t := target.NewAzureImageTarget(&target.AzureImageTargetOptions{
-				Filename:       imageType.Filename(),
-				TenantID:       azureUploadOptions.TenantId,
-				Location:       azureUploadOptions.Location,
-				SubscriptionID: azureUploadOptions.SubscriptionId,
-				ResourceGroup:  azureUploadOptions.ResourceGroup,
-			})
+		var share []string
+		if gcpUploadOptions.ShareWithAccounts != nil {
+			share = *gcpUploadOptions.ShareWithAccounts
+		}
 
-			if azureUploadOptions.ImageName != nil {
-				t.ImageName = *azureUploadOptions.ImageName
-			} else {
-				// if ImageName wasn't given, generate a random one
-				t.ImageName = fmt.Sprintf("composer-api-%s", uuid.New().String())
-			}
+		object := fmt.Sprintf("composer-api-%s", uuid.New().String())
+		t := target.NewGCPTarget(&target.GCPTargetOptions{
+			Filename:          imageType.Filename(),
+			Region:            gcpUploadOptions.Region,
+			Os:                "", // not exposed in cloudapi for now
+			Bucket:            gcpUploadOptions.Bucket,
+			Object:            object,
+			ShareWithAccounts: share,
+		})
+		// Import will fail if an image with this name already exists
+		if gcpUploadOptions.ImageName != nil {
+			t.ImageName = *gcpUploadOptions.ImageName
+		} else {
+			t.ImageName = object
+		}
 
-			targets = append(targets, t)
-		default:
-			return HTTPError(ErrorUnsupportedImageType)
+		imageRequest.target = t
+	case ImageTypes_azure:
+		var azureUploadOptions AzureUploadOptions
+		jsonUploadOptions, err := json.Marshal(ir.UploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONMarshallingError)
 		}
-	}
+		err = json.Unmarshal(jsonUploadOptions, &azureUploadOptions)
+		if err != nil {
+			return HTTPError(ErrorJSONUnMarshallingError)
+		}
+		t := target.NewAzureImageTarget(&target.AzureImageTargetOptions{
+			Filename:       imageType.Filename(),
+			TenantID:       azureUploadOptions.TenantId,
+			Location:       azureUploadOptions.Location,
+			SubscriptionID: azureUploadOptions.SubscriptionId,
+			ResourceGroup:  azureUploadOptions.ResourceGroup,
+		})
 
-	var ir imageRequest
-	if len(imageRequests) == 1 {
-		// NOTE: the store currently does not support multi-image composes
-		ir = imageRequests[0]
-	} else {
-		return HTTPError(ErrorMultiImageCompose)
+		if azureUploadOptions.ImageName != nil {
+			t.ImageName = *azureUploadOptions.ImageName
+		} else {
+			// if ImageName wasn't given, generate a random one
+			t.ImageName = fmt.Sprintf("composer-api-%s", uuid.New().String())
+		}
+
+		imageRequest.target = t
+	default:
+		return HTTPError(ErrorUnsupportedImageType)
 	}
 
-	id, err := h.server.workers.EnqueueOSBuild(ir.arch, &worker.OSBuildJob{
-		Manifest: ir.manifest,
-		Targets:  targets,
-		Exports:  ir.exports,
+	id, err := h.server.workers.EnqueueOSBuild(imageRequest.arch, &worker.OSBuildJob{
+		Manifest: imageRequest.manifest,
+		Targets:  []*target.Target{imageRequest.target},
+		Exports:  imageRequest.exports,
 	})
 	if err != nil {
 		return HTTPErrorWithInternal(ErrorEnqueueingJob, err)
diff --git a/osbuild-composer.spec b/osbuild-composer.spec
index ddf21f1da..a2bb41ff1 100644
--- a/osbuild-composer.spec
+++ b/osbuild-composer.spec
@@ -4,7 +4,7 @@
 
 %global goipath         github.com/osbuild/osbuild-composer
 
-Version:        35
+Version:        36
 
 %gometa
 


Test patch:
diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go
index 653f6a567..11644984d 100644
--- a/internal/cloudapi/v2/v2_test.go
+++ b/internal/cloudapi/v2/v2_test.go
@@ -134,7 +134,7 @@ func TestCompose(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "unsupported_distro",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "aws.ec2",
 			"repositories": [{
@@ -145,7 +145,7 @@ func TestCompose(t *testing.T) {
 				"region": "eu-central-1",
 				"share_with_accounts": ["123456789012"]
 			}
-		 }]
+		 }
 	}`, test_distro.TestArch3Name), http.StatusBadRequest, `
 	{
 		"href": "/api/image-builder-composer/v2/errors/4",
@@ -159,7 +159,7 @@ func TestCompose(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "unsupported_arch",
 			"image_type": "aws",
 			"repositories": [{
@@ -169,7 +169,7 @@ func TestCompose(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName), http.StatusBadRequest, `
 	{
 		"href": "/api/image-builder-composer/v2/errors/5",
@@ -183,7 +183,7 @@ func TestCompose(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "unsupported_image_type",
 			"repositories": [{
@@ -193,7 +193,7 @@ func TestCompose(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name), http.StatusBadRequest, `
 	{
 		"href": "/api/image-builder-composer/v2/errors/6",
@@ -230,7 +230,7 @@ func TestCompose(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "aws",
 			"repositories": [{
@@ -240,7 +240,7 @@ func TestCompose(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -258,7 +258,7 @@ func TestComposeStatusSuccess(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "aws",
 			"repositories": [{
@@ -268,7 +268,7 @@ func TestComposeStatusSuccess(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -324,7 +324,7 @@ func TestComposeStatusFailure(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "aws",
 			"repositories": [{
@@ -334,7 +334,7 @@ func TestComposeStatusFailure(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -389,7 +389,7 @@ func TestComposeCustomizations(t *testing.T) {
 				"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrGKErMYi+MMUwuHaRAJmRLoIzRf2qD2dD5z0BTx/6x"
 			}]
 		},
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "aws",
 			"repositories": [{
@@ -399,7 +399,7 @@ func TestComposeCustomizations(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -417,7 +417,7 @@ func TestImageTypes(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "%s",
 			"repositories": [{
@@ -429,7 +429,7 @@ func TestImageTypes(t *testing.T) {
 				"snapshot_name": "name",
 				"share_with_accounts": ["123456789012","234567890123"]
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_aws)), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -438,7 +438,7 @@ func TestImageTypes(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "%s",
 			"repositories": [{
@@ -448,7 +448,7 @@ func TestImageTypes(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_aws)), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -457,7 +457,7 @@ func TestImageTypes(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "%s",
 			"repositories": [{
@@ -467,7 +467,7 @@ func TestImageTypes(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_edge_commit)), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -476,7 +476,7 @@ func TestImageTypes(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "%s",
 			"repositories": [{
@@ -486,7 +486,7 @@ func TestImageTypes(t *testing.T) {
 			"upload_options": {
 				"region": "eu-central-1"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_edge_installer)), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -495,7 +495,7 @@ func TestImageTypes(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "%s",
 			"repositories": [{
@@ -508,7 +508,7 @@ func TestImageTypes(t *testing.T) {
 				"resource_group": "ToucanResourceGroup",
 				"location": "westeurope"
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_azure)), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",
@@ -517,7 +517,7 @@ func TestImageTypes(t *testing.T) {
 	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
 	{
 		"distribution": "%s",
-		"image_requests":[{
+		"image_request":{
 			"architecture": "%s",
 			"image_type": "%s",
 			"repositories": [{
@@ -529,7 +529,7 @@ func TestImageTypes(t *testing.T) {
 				"bucket": "some-eu-bucket",
 				"share_with_accounts": ["user:alice@example.com"]
 			}
-		 }]
+		 }
 	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_gcp)), http.StatusCreated, `
 	{
 		"href": "/api/image-builder-composer/v2/compose",